redis-cli.c 64.1 KB
Newer Older
A
antirez 已提交
1 2
/* Redis CLI (command line interface)
 *
3
 * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
A
antirez 已提交
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of Redis nor the names of its contributors may be used
 *     to endorse or promote products derived from this software without
 *     specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

31
#include "fmacros.h"
32
#include "version.h"
33

A
antirez 已提交
34 35 36
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
H
Henry Rawas 已提交
37
#ifndef _WIN32
A
Alex Suraci 已提交
38
#include <signal.h>
A
antirez 已提交
39
#include <unistd.h>
H
Henry Rawas 已提交
40
#endif
A
antirez 已提交
41
#include <time.h>
42
#include <ctype.h>
43
#include <errno.h>
44
#include <sys/stat.h>
H
Henry Rawas 已提交
45
#ifndef _WIN32
46
#include <sys/time.h>
H
Henry Rawas 已提交
47
#endif
48
#include <assert.h>
49
#include <fcntl.h>
H
Henry Rawas 已提交
50 51 52 53
#ifdef _WIN32
#ifndef STDIN_FILENO
  #define STDIN_FILENO (_fileno(stdin))
#endif
54
#include "win32_Interop/win32fixes.h"
55
#include "win32_Interop/Win32_ANSI.h"
56
#include <windows.h>
H
Henry Rawas 已提交
57 58 59 60 61
#define strcasecmp _stricmp
#define strncasecmp _strnicmp
#define strtoull _strtoui64
#endif

62
#include <limits.h>
A
antirez 已提交
63

H
Henry Rawas 已提交
64 65 66 67 68 69 70 71 72
#ifdef _WIN32
#include <fcntl.h>
#ifndef STDIN_FILENO
  #define STDIN_FILENO (_fileno(stdin))
#endif
#define strcasecmp _stricmp
#define strncasecmp _strnicmp
#define strtoull _strtoui64
#endif
H
Henry Rawas 已提交
73

P
Pieter Noordhuis 已提交
74
#include "hiredis.h"
A
antirez 已提交
75 76
#include "sds.h"
#include "zmalloc.h"
77
#include "linenoise.h"
78
#include "help.h"
A
antirez 已提交
79 80
#include "anet.h"
#include "ae.h"
A
antirez 已提交
81 82 83

#define REDIS_NOTUSED(V) ((void) V)

84 85 86
#define OUTPUT_STANDARD 0
#define OUTPUT_RAW 1
#define OUTPUT_CSV 2
87
#define REDIS_CLI_KEEPALIVE_INTERVAL 15 /* seconds */
88
#define REDIS_CLI_DEFAULT_PIPE_TIMEOUT 30 /* seconds */
89

P
Pieter Noordhuis 已提交
90
static redisContext *context;
A
antirez 已提交
91 92 93
static struct config {
    char *hostip;
    int hostport;
94
    char *hostsocket;
95
    long repeat;
96
    long interval;
I
ian 已提交
97
    int dbnum;
98
    int interactive;
99
    int shutdown;
100 101
    int monitor_mode;
    int pubsub_mode;
A
antirez 已提交
102
    int latency_mode;
103
    int latency_history;
104 105
    int cluster_mode;
    int cluster_reissue_command;
106
    int slave_mode;
A
antirez 已提交
107
    int pipe_mode;
A
antirez 已提交
108
    int pipe_timeout;
109
    int getrdb_mode;
110
    int stat_mode;
A
antirez 已提交
111
    int scan_mode;
112 113
    int intrinsic_latency_mode;
    int intrinsic_latency_duration;
A
antirez 已提交
114
    char *pattern;
115
    char *rdb_filename;
A
antirez 已提交
116
    int bigkeys;
117
    int stdinarg; /* get last arg from stdin. (-x option) */
A
antirez 已提交
118
    char *auth;
119
    int output; /* output mode, see OUTPUT_* defines */
P
Pieter Noordhuis 已提交
120
    sds mb_delim;
121
    char prompt[128];
A
antirez 已提交
122
    char *eval;
123
    int last_cmd_type;
A
antirez 已提交
124 125
} config;

126
static volatile sig_atomic_t force_cancel_loop = 0;
127
static void usage(void);
128
static void slaveMode(void);
129
char *redisGitSHA1(void);
130
char *redisGitDirty(void);
131

132 133 134 135
/*------------------------------------------------------------------------------
 * Utility functions
 *--------------------------------------------------------------------------- */

136
static long long ustime(void) {
A
Alexis Campailla 已提交
137 138 139
#ifdef _WIN32
    return GetHighResRelativeTime(1000000);
#else
140
    struct timeval tv;
141
    long long ust;
142 143

    gettimeofday(&tv, NULL);
144 145 146
    ust = ((long long)tv.tv_sec)*1000000;
    ust += tv.tv_usec;
    return ust;
A
Alexis Campailla 已提交
147
#endif
148 149 150 151
}

static long long mstime(void) {
    return ustime()/1000;
152 153
}

154
static void cliRefreshPrompt(void) {
155 156 157 158 159
    int len;

    if (config.hostsocket != NULL)
        len = snprintf(config.prompt,sizeof(config.prompt),"redis %s",
                       config.hostsocket);
160
    else
161 162
        len = snprintf(config.prompt,sizeof(config.prompt),
                       strchr(config.hostip,':') ? "[%s]:%d" : "%s:%d",
163 164
                       config.hostip, config.hostport);
    /* Add [dbnum] if needed */
165
    if (config.dbnum != 0 && config.last_cmd_type != REDIS_REPLY_ERROR)
166 167 168
        len += snprintf(config.prompt+len,sizeof(config.prompt)-len,"[%d]",
            config.dbnum);
    snprintf(config.prompt+len,sizeof(config.prompt)-len,"> ");
169 170
}

171 172 173 174
/*------------------------------------------------------------------------------
 * Help functions
 *--------------------------------------------------------------------------- */

175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
#define CLI_HELP_COMMAND 1
#define CLI_HELP_GROUP 2

typedef struct {
    int type;
    int argc;
    sds *argv;
    sds full;

    /* Only used for help on commands */
    struct commandHelp *org;
} helpEntry;

static helpEntry *helpEntries;
static int helpEntriesLen;

191
static sds cliVersion(void) {
192 193 194 195 196 197 198 199 200 201 202 203 204
    sds version;
    version = sdscatprintf(sdsempty(), "%s", REDIS_VERSION);

    /* Add git commit and working tree status when available */
    if (strtoll(redisGitSHA1(),NULL,16)) {
        version = sdscatprintf(version, " (git:%s", redisGitSHA1());
        if (strtoll(redisGitDirty(),NULL,10))
            version = sdscatprintf(version, "-dirty");
        version = sdscat(version, ")");
    }
    return version;
}

205
static void cliInitHelp(void) {
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
    int commandslen = sizeof(commandHelp)/sizeof(struct commandHelp);
    int groupslen = sizeof(commandGroups)/sizeof(char*);
    int i, len, pos = 0;
    helpEntry tmp;

    helpEntriesLen = len = commandslen+groupslen;
    helpEntries = malloc(sizeof(helpEntry)*len);

    for (i = 0; i < groupslen; i++) {
        tmp.argc = 1;
        tmp.argv = malloc(sizeof(sds));
        tmp.argv[0] = sdscatprintf(sdsempty(),"@%s",commandGroups[i]);
        tmp.full = tmp.argv[0];
        tmp.type = CLI_HELP_GROUP;
        tmp.org = NULL;
        helpEntries[pos++] = tmp;
    }

    for (i = 0; i < commandslen; i++) {
        tmp.argv = sdssplitargs(commandHelp[i].name,&tmp.argc);
        tmp.full = sdsnew(commandHelp[i].name);
        tmp.type = CLI_HELP_COMMAND;
        tmp.org = &commandHelp[i];
        helpEntries[pos++] = tmp;
    }
}

233
/* Output command help to stdout. */
234 235 236 237 238 239 240
static void cliOutputCommandHelp(struct commandHelp *help, int group) {
    printf("\r\n  \x1b[1m%s\x1b[0m \x1b[90m%s\x1b[0m\r\n", help->name, help->params);
    printf("  \x1b[33msummary:\x1b[0m %s\r\n", help->summary);
    printf("  \x1b[33msince:\x1b[0m %s\r\n", help->since);
    if (group) {
        printf("  \x1b[33mgroup:\x1b[0m %s\r\n", commandGroups[help->group]);
    }
241 242
}

243
/* Print generic help. */
244
static void cliOutputGenericHelp(void) {
245
    sds version = cliVersion();
246 247 248 249 250 251
    printf(
        "redis-cli %s\r\n"
        "Type: \"help @<group>\" to get a list of commands in <group>\r\n"
        "      \"help <command>\" for help on <command>\r\n"
        "      \"help <tab>\" to get a list of possible help topics\r\n"
        "      \"quit\" to exit\r\n",
252
        version
253
    );
254
    sdsfree(version);
255 256 257
}

/* Output all command help, filtering by group or command name. */
258
static void cliOutputHelp(int argc, char **argv) {
259
    int i, j, len;
260
    int group = -1;
261 262
    helpEntry *entry;
    struct commandHelp *help;
263

264 265
    if (argc == 0) {
        cliOutputGenericHelp();
266
        return;
267 268 269 270 271 272 273 274
    } else if (argc > 0 && argv[0][0] == '@') {
        len = sizeof(commandGroups)/sizeof(char*);
        for (i = 0; i < len; i++) {
            if (strcasecmp(argv[0]+1,commandGroups[i]) == 0) {
                group = i;
                break;
            }
        }
275 276
    }

277
    assert(argc > 0);
278 279 280 281 282
    for (i = 0; i < helpEntriesLen; i++) {
        entry = &helpEntries[i];
        if (entry->type != CLI_HELP_COMMAND) continue;

        help = entry->org;
283
        if (group == -1) {
284 285 286 287 288 289 290 291
            /* Compare all arguments */
            if (argc == entry->argc) {
                for (j = 0; j < argc; j++) {
                    if (strcasecmp(argv[j],entry->argv[j]) != 0) break;
                }
                if (j == argc) {
                    cliOutputCommandHelp(help,1);
                }
292 293 294
            }
        } else {
            if (group == help->group) {
295
                cliOutputCommandHelp(help,0);
296 297 298
            }
        }
    }
299 300 301 302 303 304 305 306
    printf("\r\n");
}

static void completionCallback(const char *buf, linenoiseCompletions *lc) {
    size_t startpos = 0;
    int mask;
    int i;
    size_t matchlen;
307
    sds tmp;
308 309 310 311

    if (strncasecmp(buf,"help ",5) == 0) {
        startpos = 5;
        while (isspace(buf[startpos])) startpos++;
312
        mask = CLI_HELP_COMMAND | CLI_HELP_GROUP;
313
    } else {
314
        mask = CLI_HELP_COMMAND;
315 316
    }

317 318
    for (i = 0; i < helpEntriesLen; i++) {
        if (!(helpEntries[i].type & mask)) continue;
319 320

        matchlen = strlen(buf+startpos);
321 322 323
        if (strncasecmp(buf+startpos,helpEntries[i].full,matchlen) == 0) {
            tmp = sdsnewlen(buf,startpos);
            tmp = sdscat(tmp,helpEntries[i].full);
324
            linenoiseAddCompletion(lc,tmp);
325
            sdsfree(tmp);
326 327
        }
    }
328 329
}

330 331 332 333
/*------------------------------------------------------------------------------
 * Networking / parsing
 *--------------------------------------------------------------------------- */

P
Pieter Noordhuis 已提交
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
/* Send AUTH command to the server */
static int cliAuth() {
    redisReply *reply;
    if (config.auth == NULL) return REDIS_OK;

    reply = redisCommand(context,"AUTH %s",config.auth);
    if (reply != NULL) {
        freeReplyObject(reply);
        return REDIS_OK;
    }
    return REDIS_ERR;
}

/* Send SELECT dbnum to the server */
static int cliSelect() {
    redisReply *reply;
    if (config.dbnum == 0) return REDIS_OK;

352
    reply = redisCommand(context,"SELECT %d",config.dbnum);
P
Pieter Noordhuis 已提交
353
    if (reply != NULL) {
354 355
        int result = REDIS_OK;
        if (reply->type == REDIS_REPLY_ERROR) result = REDIS_ERR;
P
Pieter Noordhuis 已提交
356
        freeReplyObject(reply);
357
        return result;
P
Pieter Noordhuis 已提交
358 359 360 361
    }
    return REDIS_ERR;
}

G
guiquanz 已提交
362
/* Connect to the server. If force is not zero the connection is performed
363 364
 * even if there is already a connected socket. */
static int cliConnect(int force) {
P
Pieter Noordhuis 已提交
365 366 367
    if (context == NULL || force) {
        if (context != NULL)
            redisFree(context);
A
antirez 已提交
368

369
        if (config.hostsocket == NULL) {
P
Pieter Noordhuis 已提交
370
            context = redisConnect(config.hostip,config.hostport);
371
        } else {
P
Pieter Noordhuis 已提交
372
            context = redisConnectUnix(config.hostsocket);
373
        }
P
Pieter Noordhuis 已提交
374 375

        if (context->err) {
376 377
            fprintf(stderr,"Could not connect to Redis at ");
            if (config.hostsocket == NULL)
P
Pieter Noordhuis 已提交
378
                fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,context->errstr);
379
            else
P
Pieter Noordhuis 已提交
380 381 382 383
                fprintf(stderr,"%s: %s\n",config.hostsocket,context->errstr);
            redisFree(context);
            context = NULL;
            return REDIS_ERR;
384
        }
A
antirez 已提交
385

386 387 388 389 390 391
        /* Set aggressive KEEP_ALIVE socket option in the Redis context socket
         * in order to prevent timeouts caused by the execution of long
         * commands. At the same time this improves the detection of real
         * errors. */
        anetKeepAlive(NULL, context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);

P
Pieter Noordhuis 已提交
392 393 394 395 396
        /* Do AUTH and select the right DB. */
        if (cliAuth() != REDIS_OK)
            return REDIS_ERR;
        if (cliSelect() != REDIS_OK)
            return REDIS_ERR;
A
antirez 已提交
397
    }
P
Pieter Noordhuis 已提交
398
    return REDIS_OK;
A
antirez 已提交
399 400
}

401
static void cliPrintContextError(void) {
P
Pieter Noordhuis 已提交
402 403
    if (context == NULL) return;
    fprintf(stderr,"Error: %s\n",context->errstr);
A
antirez 已提交
404 405
}

P
Pieter Noordhuis 已提交
406
static sds cliFormatReplyTTY(redisReply *r, char *prefix) {
P
Pieter Noordhuis 已提交
407 408 409
    sds out = sdsempty();
    switch (r->type) {
    case REDIS_REPLY_ERROR:
P
Pieter Noordhuis 已提交
410
        out = sdscatprintf(out,"(error) %s\n", r->str);
P
Pieter Noordhuis 已提交
411 412 413 414 415 416
    break;
    case REDIS_REPLY_STATUS:
        out = sdscat(out,r->str);
        out = sdscat(out,"\n");
    break;
    case REDIS_REPLY_INTEGER:
P
Pieter Noordhuis 已提交
417
        out = sdscatprintf(out,"(integer) %lld\n",r->integer);
P
Pieter Noordhuis 已提交
418 419
    break;
    case REDIS_REPLY_STRING:
P
Pieter Noordhuis 已提交
420 421 422 423
        /* If you are producing output for the standard output we want
        * a more interesting output with quoted characters and so forth */
        out = sdscatrepr(out,r->str,r->len);
        out = sdscat(out,"\n");
P
Pieter Noordhuis 已提交
424 425 426 427 428 429 430
    break;
    case REDIS_REPLY_NIL:
        out = sdscat(out,"(nil)\n");
    break;
    case REDIS_REPLY_ARRAY:
        if (r->elements == 0) {
            out = sdscat(out,"(empty list or set)\n");
431
        } else {
432 433 434 435
            unsigned int i, idxlen = 0;
            char _prefixlen[16];
            char _prefixfmt[16];
            sds _prefix;
P
Pieter Noordhuis 已提交
436 437
            sds tmp;

438
            /* Calculate chars needed to represent the largest index */
H
Henry Rawas 已提交
439
            i = (unsigned int)r->elements;
440 441 442 443 444 445 446 447 448 449 450 451 452
            do {
                idxlen++;
                i /= 10;
            } while(i);

            /* Prefix for nested multi bulks should grow with idxlen+2 spaces */
            memset(_prefixlen,' ',idxlen+2);
            _prefixlen[idxlen+2] = '\0';
            _prefix = sdscat(sdsnew(prefix),_prefixlen);

            /* Setup prefix format for every entry */
            snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%dd) ",idxlen);

P
Pieter Noordhuis 已提交
453
            for (i = 0; i < r->elements; i++) {
454 455 456 457 458
                /* Don't use the prefix for the first element, as the parent
                 * caller already prepended the index number. */
                out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,i+1);

                /* Format the multi bulk entry */
P
Pieter Noordhuis 已提交
459
                tmp = cliFormatReplyTTY(r->element[i],_prefix);
P
Pieter Noordhuis 已提交
460 461 462
                out = sdscatlen(out,tmp,sdslen(tmp));
                sdsfree(tmp);
            }
463
            sdsfree(_prefix);
464
        }
P
Pieter Noordhuis 已提交
465
    break;
466
    default:
P
Pieter Noordhuis 已提交
467 468
        fprintf(stderr,"Unknown reply type: %d\n", r->type);
        exit(1);
469
    }
P
Pieter Noordhuis 已提交
470
    return out;
471 472
}

P
Pieter Noordhuis 已提交
473 474 475 476 477 478 479
static sds cliFormatReplyRaw(redisReply *r) {
    sds out = sdsempty(), tmp;
    size_t i;

    switch (r->type) {
    case REDIS_REPLY_NIL:
        /* Nothing... */
A
antirez 已提交
480
        break;
P
Pieter Noordhuis 已提交
481
    case REDIS_REPLY_ERROR:
A
antirez 已提交
482 483 484
        out = sdscatlen(out,r->str,r->len);
        out = sdscatlen(out,"\n",1);
        break;
P
Pieter Noordhuis 已提交
485 486 487
    case REDIS_REPLY_STATUS:
    case REDIS_REPLY_STRING:
        out = sdscatlen(out,r->str,r->len);
A
antirez 已提交
488
        break;
P
Pieter Noordhuis 已提交
489 490
    case REDIS_REPLY_INTEGER:
        out = sdscatprintf(out,"%lld",r->integer);
A
antirez 已提交
491
        break;
P
Pieter Noordhuis 已提交
492 493 494 495 496 497 498
    case REDIS_REPLY_ARRAY:
        for (i = 0; i < r->elements; i++) {
            if (i > 0) out = sdscat(out,config.mb_delim);
            tmp = cliFormatReplyRaw(r->element[i]);
            out = sdscatlen(out,tmp,sdslen(tmp));
            sdsfree(tmp);
        }
A
antirez 已提交
499
        break;
P
Pieter Noordhuis 已提交
500 501 502 503 504 505 506
    default:
        fprintf(stderr,"Unknown reply type: %d\n", r->type);
        exit(1);
    }
    return out;
}

507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542
static sds cliFormatReplyCSV(redisReply *r) {
    unsigned int i;

    sds out = sdsempty();
    switch (r->type) {
    case REDIS_REPLY_ERROR:
        out = sdscat(out,"ERROR,");
        out = sdscatrepr(out,r->str,strlen(r->str));
    break;
    case REDIS_REPLY_STATUS:
        out = sdscatrepr(out,r->str,r->len);
    break;
    case REDIS_REPLY_INTEGER:
        out = sdscatprintf(out,"%lld",r->integer);
    break;
    case REDIS_REPLY_STRING:
        out = sdscatrepr(out,r->str,r->len);
    break;
    case REDIS_REPLY_NIL:
        out = sdscat(out,"NIL\n");
    break;
    case REDIS_REPLY_ARRAY:
        for (i = 0; i < r->elements; i++) {
            sds tmp = cliFormatReplyCSV(r->element[i]);
            out = sdscatlen(out,tmp,sdslen(tmp));
            if (i != r->elements-1) out = sdscat(out,",");
            sdsfree(tmp);
        }
    break;
    default:
        fprintf(stderr,"Unknown reply type: %d\n", r->type);
        exit(1);
    }
    return out;
}

P
Pieter Noordhuis 已提交
543
static int cliReadReply(int output_raw_strings) {
544
    void *_reply;
P
Pieter Noordhuis 已提交
545
    redisReply *reply;
546
    sds out = NULL;
547
    int output = 1;
P
Pieter Noordhuis 已提交
548

549
    if (redisGetReply(context,&_reply) != REDIS_OK) {
550 551 552
        if (config.shutdown) {
            redisFree(context);
            context = NULL;
P
Pieter Noordhuis 已提交
553
            return REDIS_OK;
554
        }
P
Pieter Noordhuis 已提交
555 556
        if (config.interactive) {
            /* Filter cases where we should reconnect */
557 558
            if (context->err == REDIS_ERR_IO &&
                (errno == ECONNRESET || errno == EPIPE))
P
Pieter Noordhuis 已提交
559 560 561 562
                return REDIS_ERR;
            if (context->err == REDIS_ERR_EOF)
                return REDIS_ERR;
        }
563 564
        cliPrintContextError();
        exit(1);
P
Pieter Noordhuis 已提交
565
        return REDIS_ERR; /* avoid compiler warning */
I
ian 已提交
566
    }
P
Pieter Noordhuis 已提交
567

568
    reply = (redisReply*)_reply;
569

570 571
    config.last_cmd_type = reply->type;

572 573
    /* Check if we need to connect to a different node and reissue the
     * request. */
574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602
    if (config.cluster_mode && reply->type == REDIS_REPLY_ERROR &&
        (!strncmp(reply->str,"MOVED",5) || !strcmp(reply->str,"ASK")))
    {
        char *p = reply->str, *s;
        int slot;

        output = 0;
        /* Comments show the position of the pointer as:
         *
         * [S] for pointer 's'
         * [P] for pointer 'p'
         */
        s = strchr(p,' ');      /* MOVED[S]3999 127.0.0.1:6381 */
        p = strchr(s+1,' ');    /* MOVED[S]3999[P]127.0.0.1:6381 */
        *p = '\0';
        slot = atoi(s+1);
        s = strchr(p+1,':');    /* MOVED 3999[P]127.0.0.1[S]6381 */
        *s = '\0';
        sdsfree(config.hostip);
        config.hostip = sdsnew(p+1);
        config.hostport = atoi(s+1);
        if (config.interactive)
            printf("-> Redirected to slot [%d] located at %s:%d\n",
                slot, config.hostip, config.hostport);
        config.cluster_reissue_command = 1;
    }

    if (output) {
        if (output_raw_strings) {
P
Pieter Noordhuis 已提交
603 604
            out = cliFormatReplyRaw(reply);
        } else {
605
            if (config.output == OUTPUT_RAW) {
606 607
                out = cliFormatReplyRaw(reply);
                out = sdscat(out,"\n");
608
            } else if (config.output == OUTPUT_STANDARD) {
609
                out = cliFormatReplyTTY(reply,"");
610 611 612
            } else if (config.output == OUTPUT_CSV) {
                out = cliFormatReplyCSV(reply);
                out = sdscat(out,"\n");
613
            }
P
Pieter Noordhuis 已提交
614
        }
H
Henry Rawas 已提交
615 616 617 618
#ifdef _WIN32
        /* if size is too large, fwrite fails. Use fprintf */
        fprintf(stdout, "%s", out);
#else
619
        fwrite(out,sdslen(out),1,stdout);
H
Henry Rawas 已提交
620
#endif
621
        sdsfree(out);
P
Pieter Noordhuis 已提交
622 623
    }
    freeReplyObject(reply);
P
Pieter Noordhuis 已提交
624
    return REDIS_OK;
I
ian 已提交
625 626
}

627
static int cliSendCommand(int argc, char **argv, int repeat) {
628
    char *command = argv[0];
P
Pieter Noordhuis 已提交
629
    size_t *argvlen;
P
Pieter Noordhuis 已提交
630
    int j, output_raw;
A
antirez 已提交
631

632 633 634 635 636
    if (!strcasecmp(command,"help") || !strcasecmp(command,"?")) {
        cliOutputHelp(--argc, ++argv);
        return REDIS_OK;
    }

637
    if (context == NULL) return REDIS_ERR;
A
antirez 已提交
638

A
antirez 已提交
639 640 641 642
    output_raw = 0;
    if (!strcasecmp(command,"info") ||
        (argc == 2 && !strcasecmp(command,"cluster") &&
                      (!strcasecmp(argv[1],"nodes") ||
A
antirez 已提交
643 644
                       !strcasecmp(argv[1],"info"))) ||
        (argc == 2 && !strcasecmp(command,"client") &&
A
antirez 已提交
645 646
                       !strcasecmp(argv[1],"list")) ||
        (argc == 3 && !strcasecmp(command,"latency") &&
647 648 649
                       !strcasecmp(argv[1],"graph")) ||
        (argc == 2 && !strcasecmp(command,"latency") &&
                       !strcasecmp(argv[1],"doctor")))
A
antirez 已提交
650 651 652 653
    {
        output_raw = 1;
    }

654 655 656 657
    if (!strcasecmp(command,"shutdown")) config.shutdown = 1;
    if (!strcasecmp(command,"monitor")) config.monitor_mode = 1;
    if (!strcasecmp(command,"subscribe") ||
        !strcasecmp(command,"psubscribe")) config.pubsub_mode = 1;
658 659
    if (!strcasecmp(command,"sync") ||
        !strcasecmp(command,"psync")) config.slave_mode = 1;
A
antirez 已提交
660

P
Pieter Noordhuis 已提交
661 662 663 664
    /* Setup argument length */
    argvlen = malloc(argc*sizeof(size_t));
    for (j = 0; j < argc; j++)
        argvlen[j] = sdslen(argv[j]);
665

666
    while(repeat--) {
P
Pieter Noordhuis 已提交
667
        redisAppendCommandArgv(context,argc,(const char**)argv,argvlen);
668
        while (config.monitor_mode) {
P
Pieter Noordhuis 已提交
669
            if (cliReadReply(output_raw) != REDIS_OK) exit(1);
670
            fflush(stdout);
671 672
        }

673
        if (config.pubsub_mode) {
674
            if (config.output != OUTPUT_RAW)
P
Pieter Noordhuis 已提交
675
                printf("Reading messages... (press Ctrl-C to quit)\n");
676
            while (1) {
P
Pieter Noordhuis 已提交
677
                if (cliReadReply(output_raw) != REDIS_OK) exit(1);
678 679 680
            }
        }

681 682 683 684
        if (config.slave_mode) {
            printf("Entering slave output mode...  (press Ctrl-C to quit)\n");
            slaveMode();
            config.slave_mode = 0;
685
            free(argvlen);
686 687 688
            return REDIS_ERR;  /* Error = slaveMode lost connection to master */
        }

689 690
        if (cliReadReply(output_raw) != REDIS_OK) {
            free(argvlen);
P
Pieter Noordhuis 已提交
691
            return REDIS_ERR;
692 693
        } else {
            /* Store database number when SELECT was successfully executed. */
694
            if (!strcasecmp(command,"select") && argc == 2) {
695
                config.dbnum = atoi(argv[1]);
696
                cliRefreshPrompt();
697 698
            } else if (!strcasecmp(command,"auth") && argc == 2) {
                cliSelect();
699
            }
700
        }
701 702
        if (config.interval) usleep(config.interval);
        fflush(stdout); /* Make it grep friendly */
A
antirez 已提交
703
    }
704 705

    free(argvlen);
P
Pieter Noordhuis 已提交
706
    return REDIS_OK;
A
antirez 已提交
707 708
}

709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738
/* Send the INFO command, reconnecting the link if needed. */
static redisReply *reconnectingInfo(void) {
    redisContext *c = context;
    redisReply *reply = NULL;
    int tries = 0;

    assert(!c->err);
    while(reply == NULL) {
        while (c->err & (REDIS_ERR_IO | REDIS_ERR_EOF)) {
            printf("Reconnecting (%d)...\r", ++tries);
            fflush(stdout);

            redisFree(c);
            c = redisConnect(config.hostip,config.hostport);
            usleep(1000000);
        }

        reply = redisCommand(c,"INFO");
        if (c->err && !(c->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
            fprintf(stderr, "Error: %s\n", c->errstr);
            exit(1);
        } else if (tries > 0) {
            printf("\n");
        }
    }

    context = c;
    return reply;
}

739 740 741 742
/*------------------------------------------------------------------------------
 * User interface
 *--------------------------------------------------------------------------- */

A
antirez 已提交
743 744 745 746 747
static int parseOptions(int argc, char **argv) {
    int i;

    for (i = 1; i < argc; i++) {
        int lastarg = i==argc-1;
748

A
antirez 已提交
749
        if (!strcmp(argv[i],"-h") && !lastarg) {
A
antirez 已提交
750
            sdsfree(config.hostip);
A
antirez 已提交
751
            config.hostip = sdsnew(argv[++i]);
A
antirez 已提交
752 753
        } else if (!strcmp(argv[i],"-h") && lastarg) {
            usage();
754 755
        } else if (!strcmp(argv[i],"--help")) {
            usage();
756 757
        } else if (!strcmp(argv[i],"-x")) {
            config.stdinarg = 1;
A
antirez 已提交
758
        } else if (!strcmp(argv[i],"-p") && !lastarg) {
A
antirez 已提交
759
            config.hostport = atoi(argv[++i]);
760
        } else if (!strcmp(argv[i],"-s") && !lastarg) {
A
antirez 已提交
761
            config.hostsocket = argv[++i];
762
        } else if (!strcmp(argv[i],"-r") && !lastarg) {
H
Henry Rawas 已提交
763
            config.repeat = (long)strtoll(argv[++i],NULL,10);
764
        } else if (!strcmp(argv[i],"-i") && !lastarg) {
A
antirez 已提交
765
            double seconds = atof(argv[++i]);
H
Henry Rawas 已提交
766
            config.interval = (long)(seconds*1000000);
I
ian 已提交
767
        } else if (!strcmp(argv[i],"-n") && !lastarg) {
A
antirez 已提交
768
            config.dbnum = atoi(argv[++i]);
769
        } else if (!strcmp(argv[i],"-a") && !lastarg) {
A
antirez 已提交
770
            config.auth = argv[++i];
P
Pieter Noordhuis 已提交
771
        } else if (!strcmp(argv[i],"--raw")) {
772
            config.output = OUTPUT_RAW;
M
Matt Stancliff 已提交
773 774
        } else if (!strcmp(argv[i],"--no-raw")) {
            config.output = OUTPUT_STANDARD;
775 776
        } else if (!strcmp(argv[i],"--csv")) {
            config.output = OUTPUT_CSV;
A
antirez 已提交
777 778
        } else if (!strcmp(argv[i],"--latency")) {
            config.latency_mode = 1;
779 780 781
        } else if (!strcmp(argv[i],"--latency-history")) {
            config.latency_mode = 1;
            config.latency_history = 1;
782 783
        } else if (!strcmp(argv[i],"--slave")) {
            config.slave_mode = 1;
784 785
        } else if (!strcmp(argv[i],"--stat")) {
            config.stat_mode = 1;
A
antirez 已提交
786 787
        } else if (!strcmp(argv[i],"--scan")) {
            config.scan_mode = 1;
788
        } else if (!strcmp(argv[i],"--pattern") && !lastarg) {
A
antirez 已提交
789
            config.pattern = argv[++i];
790 791 792
        } else if (!strcmp(argv[i],"--intrinsic-latency") && !lastarg) {
            config.intrinsic_latency_mode = 1;
            config.intrinsic_latency_duration = atoi(argv[++i]);
793 794 795
        } else if (!strcmp(argv[i],"--rdb") && !lastarg) {
            config.getrdb_mode = 1;
            config.rdb_filename = argv[++i];
A
antirez 已提交
796 797
        } else if (!strcmp(argv[i],"--pipe")) {
            config.pipe_mode = 1;
A
antirez 已提交
798 799
        } else if (!strcmp(argv[i],"--pipe-timeout") && !lastarg) {
            config.pipe_timeout = atoi(argv[++i]);
A
antirez 已提交
800 801
        } else if (!strcmp(argv[i],"--bigkeys")) {
            config.bigkeys = 1;
A
antirez 已提交
802 803
        } else if (!strcmp(argv[i],"--eval") && !lastarg) {
            config.eval = argv[++i];
804 805
        } else if (!strcmp(argv[i],"-c")) {
            config.cluster_mode = 1;
806 807
        } else if (!strcmp(argv[i],"-d") && !lastarg) {
            sdsfree(config.mb_delim);
A
antirez 已提交
808
            config.mb_delim = sdsnew(argv[++i]);
809 810 811 812
        } else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) {
            sds version = cliVersion();
            printf("redis-cli %s\n", version);
            sdsfree(version);
813
            exit(0);
A
antirez 已提交
814
        } else {
815 816 817 818 819 820 821 822 823
            if (argv[i][0] == '-') {
                fprintf(stderr,
                    "Unrecognized option or bad number of args for: '%s'\n",
                    argv[i]);
                exit(1);
            } else {
                /* Likely the command name, stop here. */
                break;
            }
A
antirez 已提交
824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845
        }
    }
    return i;
}

static sds readArgFromStdin(void) {
    char buf[1024];
    sds arg = sdsempty();

    while(1) {
        int nread = read(fileno(stdin),buf,1024);

        if (nread == 0) break;
        else if (nread == -1) {
            perror("Reading from standard input");
            exit(1);
        }
        arg = sdscatlen(arg,buf,nread);
    }
    return arg;
}

846
static void usage(void) {
847 848 849 850 851
    sds version = cliVersion();
    fprintf(stderr,
"redis-cli %s\n"
"\n"
"Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]\n"
852 853 854 855 856
"  -h <hostname>      Server hostname (default: 127.0.0.1).\n"
"  -p <port>          Server port (default: 6379).\n"
"  -s <socket>        Server socket (overrides hostname and port).\n"
"  -a <password>      Password to use when connecting to the server.\n"
"  -r <repeat>        Execute specified command N times.\n"
A
antirez 已提交
857
"  -i <interval>      When -r is used, waits <interval> seconds per command.\n"
858 859 860 861 862
"                     It is possible to specify sub-second times like -i 0.1.\n"
"  -n <db>            Database number.\n"
"  -x                 Read last argument from STDIN.\n"
"  -d <delimiter>     Multi-bulk delimiter in for raw formatting (default: \\n).\n"
"  -c                 Enable cluster mode (follow -ASK and -MOVED redirections).\n"
A
antirez 已提交
863
"  --raw              Use raw formatting for replies (default when STDOUT is\n"
864
"                     not a tty).\n"
M
Matt Stancliff 已提交
865
"  --no-raw           Force formatted output even when STDOUT is not a tty.\n"
866
"  --csv              Output in CSV format.\n"
867
"  --stat             Print rolling stats about server: mem, clients, ...\n"
868
"  --latency          Enter a special mode continuously sampling latency.\n"
A
antirez 已提交
869 870
"  --latency-history  Like --latency but tracking latency changes over time.\n"
"                     Default time interval is 15 sec. Change it using -i.\n"
871
"  --slave            Simulate a slave showing commands received from the master.\n"
A
antirez 已提交
872
"  --rdb <filename>   Transfer an RDB dump from remote server to local file.\n"
873 874
"  --pipe             Transfer raw Redis protocol from stdin to server.\n"
"  --pipe-timeout <n> In --pipe mode, abort with error if after sending all data.\n"
A
antirez 已提交
875 876
"                     no reply is received within <n> seconds.\n"
"                     Default timeout: %d. Use 0 to wait forever.\n"
877 878 879
"  --bigkeys          Sample Redis keys looking for big keys.\n"
"  --scan             List all keys using the SCAN command.\n"
"  --pattern <pat>    Useful with --scan to specify a SCAN pattern.\n"
880 881
"  --intrinsic-latency <sec> Run a test to measure intrinsic system latency.\n"
"                     The test will run for the specified amount of seconds.\n"
882 883 884
"  --eval <file>      Send an EVAL command using the Lua script at <file>.\n"
"  --help             Output this help and exit.\n"
"  --version          Output version and exit.\n"
885 886 887 888 889
"\n"
"Examples:\n"
"  cat /etc/passwd | redis-cli -x set mypasswd\n"
"  redis-cli get mypasswd\n"
"  redis-cli -r 100 lpush mylist x\n"
890
"  redis-cli -r 100 -i 1 info | grep used_memory_human:\n"
A
antirez 已提交
891
"  redis-cli --eval myscript.lua key1 key2 , arg1 arg2 arg3\n"
892 893
"  redis-cli --scan --pattern '*:12345*'\n"
"\n"
A
antirez 已提交
894
"  (Note: when using --eval the comma separates KEYS[] from ARGV[] items)\n"
895 896 897 898
"\n"
"When no command is given, redis-cli starts in interactive mode.\n"
"Type \"help\" in interactive mode for information on available commands.\n"
"\n",
899
        version, REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
900
    sdsfree(version);
A
antirez 已提交
901 902 903
    exit(1);
}

904 905 906
/* Turn the plain C strings into Sds strings */
static char **convertToSds(int count, char** args) {
  int j;
907
  char **sds = zmalloc(sizeof(char*)*count);
908 909 910 911 912 913 914

  for(j = 0; j < count; j++)
    sds[j] = sdsnew(args[j]);

  return sds;
}

915
static void repl(void) {
916 917
    sds historyfile = NULL;
    int history = 0;
918
    char *line;
919
    int argc;
920
    sds *argv;
921

922
    config.interactive = 1;
923
    linenoiseSetMultiLine(1);
924
    linenoiseSetCompletionCallback(completionCallback);
925

926 927 928 929
    /* Only use history when stdin is a tty. */
    if (isatty(fileno(stdin))) {
        history = 1;

H
Henry Rawas 已提交
930 931 932 933 934 935
#ifdef _WIN32
        if (getenv("USERPROFILE") != NULL) {
            historyfile = sdscatprintf(sdsempty(),"%s\\.rediscli_history",getenv("USERPROFILE"));
            linenoiseHistoryLoad(historyfile);
        }
#else
936 937 938 939
        if (getenv("HOME") != NULL) {
            historyfile = sdscatprintf(sdsempty(),"%s/.rediscli_history",getenv("HOME"));
            linenoiseHistoryLoad(historyfile);
        }
H
Henry Rawas 已提交
940
#endif
941 942
    }

943 944
    cliRefreshPrompt();
    while((line = linenoise(context ? config.prompt : "not connected> ")) != NULL) {
945
        if (line[0] != '\0') {
946
            argv = sdssplitargs(line,&argc);
947 948 949
            if (history) linenoiseHistoryAdd(line);
            if (historyfile) linenoiseHistorySave(historyfile);

950 951
            if (argv == NULL) {
                printf("Invalid argument(s)\n");
A
antirez 已提交
952
                free(line);
953 954
                continue;
            } else if (argc > 0) {
955 956
                if (strcasecmp(argv[0],"quit") == 0 ||
                    strcasecmp(argv[0],"exit") == 0)
957 958
                {
                    exit(0);
A
antirez 已提交
959 960 961 962
                } else if (argc == 3 && !strcasecmp(argv[0],"connect")) {
                    sdsfree(config.hostip);
                    config.hostip = sdsnew(argv[1]);
                    config.hostport = atoi(argv[2]);
C
charsyam 已提交
963
                    cliRefreshPrompt();
A
antirez 已提交
964
                    cliConnect(1);
965 966
                } else if (argc == 1 && !strcasecmp(argv[0],"clear")) {
                    linenoiseClearScreen();
967
                } else {
968
                    long long start_time = mstime(), elapsed;
969
                    int repeat, skipargs = 0;
970

971
                    repeat = atoi(argv[0]);
972
                    if (argc > 1 && repeat) {
973 974 975 976 977
                        skipargs = 1;
                    } else {
                        repeat = 1;
                    }

978 979
                    while (1) {
                        config.cluster_reissue_command = 0;
980 981
                        if (cliSendCommand(argc-skipargs,argv+skipargs,repeat)
                            != REDIS_OK)
982 983 984 985 986 987 988 989 990 991 992 993 994 995 996
                        {
                            cliConnect(1);

                            /* If we still cannot send the command print error.
                             * We'll try to reconnect the next time. */
                            if (cliSendCommand(argc-skipargs,argv+skipargs,repeat)
                                != REDIS_OK)
                                cliPrintContextError();
                        }
                        /* Issue the command again if we got redirected in cluster mode */
                        if (config.cluster_mode && config.cluster_reissue_command) {
                            cliConnect(1);
                        } else {
                            break;
                        }
997
                    }
998
                    elapsed = mstime()-start_time;
P
Pieter Noordhuis 已提交
999 1000 1001
                    if (elapsed >= 500) {
                        printf("(%.2fs)\n",(double)elapsed/1000);
                    }
1002
                }
1003 1004
            }
            /* Free the argument vector */
1005
            sdsfreesplitres(argv,argc);
1006
        }
1007
        /* linenoise() returns malloc-ed lines like readline() */
1008
        free(line);
1009 1010 1011 1012
    }
    exit(0);
}

1013 1014
static int noninteractive(int argc, char **argv) {
    int retval = 0;
1015
    if (config.stdinarg) {
1016 1017 1018 1019 1020 1021 1022 1023 1024 1025
        argv = zrealloc(argv, (argc+1)*sizeof(char*));
        argv[argc] = readArgFromStdin();
        retval = cliSendCommand(argc+1, argv, config.repeat);
    } else {
        /* stdin is probably a tty, can be tested with S_ISCHR(s.st_mode) */
        retval = cliSendCommand(argc, argv, config.repeat);
    }
    return retval;
}

1026 1027 1028 1029
/*------------------------------------------------------------------------------
 * Eval mode
 *--------------------------------------------------------------------------- */

A
antirez 已提交
1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067
static int evalMode(int argc, char **argv) {
    sds script = sdsempty();
    FILE *fp;
    char buf[1024];
    size_t nread;
    char **argv2;
    int j, got_comma = 0, keys = 0;

    /* Load the script from the file, as an sds string. */
    fp = fopen(config.eval,"r");
    if (!fp) {
        fprintf(stderr,
            "Can't open file '%s': %s\n", config.eval, strerror(errno));
        exit(1);
    }
    while((nread = fread(buf,1,sizeof(buf),fp)) != 0) {
        script = sdscatlen(script,buf,nread);
    }
    fclose(fp);

    /* Create our argument vector */
    argv2 = zmalloc(sizeof(sds)*(argc+3));
    argv2[0] = sdsnew("EVAL");
    argv2[1] = script;
    for (j = 0; j < argc; j++) {
        if (!got_comma && argv[j][0] == ',' && argv[j][1] == 0) {
            got_comma = 1;
            continue;
        }
        argv2[j+3-got_comma] = sdsnew(argv[j]);
        if (!got_comma) keys++;
    }
    argv2[2] = sdscatprintf(sdsempty(),"%d",keys);

    /* Call it */
    return cliSendCommand(argc+3-got_comma, argv2, config.repeat);
}

1068 1069 1070 1071
/*------------------------------------------------------------------------------
 * Latency and latency history modes
 *--------------------------------------------------------------------------- */

1072 1073
#define LATENCY_SAMPLE_RATE 10 /* milliseconds. */
#define LATENCY_HISTORY_DEFAULT_INTERVAL 15000 /* milliseconds. */
A
antirez 已提交
1074 1075
static void latencyMode(void) {
    redisReply *reply;
A
antirez 已提交
1076
    long long start, latency, min = 0, max = 0, tot = 0, count = 0;
1077 1078 1079
    long long history_interval =
        config.interval ? config.interval/1000 :
                          LATENCY_HISTORY_DEFAULT_INTERVAL;
A
antirez 已提交
1080
    double avg;
1081
    long long history_start = mstime();
A
antirez 已提交
1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099

    if (!context) exit(1);
    while(1) {
        start = mstime();
        reply = redisCommand(context,"PING");
        if (reply == NULL) {
            fprintf(stderr,"\nI/O error\n");
            exit(1);
        }
        latency = mstime()-start;
        freeReplyObject(reply);
        count++;
        if (count == 1) {
            min = max = tot = latency;
            avg = (double) latency;
        } else {
            if (latency < min) min = latency;
            if (latency > max) max = latency;
1100
            tot += latency;
A
antirez 已提交
1101 1102 1103 1104 1105
            avg = (double) tot/count;
        }
        printf("\x1b[0G\x1b[2Kmin: %lld, max: %lld, avg: %.2f (%lld samples)",
            min, max, avg, count);
        fflush(stdout);
1106 1107 1108 1109 1110 1111 1112
        if (config.latency_history && mstime()-history_start > history_interval)
        {
            printf(" -- %.2f seconds range\n", (float)(mstime()-history_start)/1000);
            history_start = mstime();
            min = max = tot = count = 0;
        }
        usleep(LATENCY_SAMPLE_RATE * 1000);
A
antirez 已提交
1113 1114 1115
    }
}

1116 1117 1118 1119
/*------------------------------------------------------------------------------
 * Slave mode
 *--------------------------------------------------------------------------- */

1120 1121 1122
/* Sends SYNC and reads the number of bytes in the payload. Used both by
 * slaveMode() and getRDB(). */
unsigned long long sendSync(int fd) {
1123 1124 1125 1126
    /* To start we need to send the SYNC command and return the payload.
     * The hiredis client lib does not understand this part of the protocol
     * and we don't want to mess with its buffers, so everything is performed
     * using direct low-level I/O. */
1127
    char buf[4096], *p;
1128 1129 1130
    ssize_t nread;

    /* Send the SYNC command. */
1131 1132 1133 1134
    if (write(fd,"SYNC\r\n",6) != 6) {
        fprintf(stderr,"Error writing to master\n");
        exit(1);
    }
1135 1136 1137 1138 1139 1140 1141 1142 1143

    /* Read $<payload>\r\n, making sure to read just up to "\n" */
    p = buf;
    while(1) {
        nread = read(fd,p,1);
        if (nread <= 0) {
            fprintf(stderr,"Error reading bulk length while SYNCing\n");
            exit(1);
        }
1144 1145
        if (*p == '\n' && p != buf) break;
        if (*p != '\n') p++;
1146 1147
    }
    *p = '\0';
1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158
    if (buf[0] == '-') {
        printf("SYNC with master failed: %s\n", buf);
        exit(1);
    }
    return strtoull(buf+1,NULL,10);
}

static void slaveMode(void) {
    int fd = context->fd;
    unsigned long long payload = sendSync(fd);
    char buf[1024];
1159
    int original_output = config.output;
1160 1161

    fprintf(stderr,"SYNC with master, discarding %llu "
G
guiquanz 已提交
1162
                   "bytes of bulk transfer...\n", payload);
1163 1164 1165

    /* Discard the payload. */
    while(payload) {
1166
        ssize_t nread;
H
Henry Rawas 已提交
1167 1168 1169
#ifdef _WIN32
        nread = read(fd,buf,(payload > sizeof(buf)) ? sizeof(buf) : (unsigned int)payload);
#else
1170
        nread = read(fd,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload);
H
Henry Rawas 已提交
1171
#endif
1172 1173 1174 1175 1176 1177
        if (nread <= 0) {
            fprintf(stderr,"Error reading RDB payload while SYNCing\n");
            exit(1);
        }
        payload -= nread;
    }
1178
    fprintf(stderr,"SYNC done. Logging commands from master.\n");
1179

1180
    /* Now we can use hiredis to read the incoming protocol. */
1181 1182
    config.output = OUTPUT_CSV;
    while (cliReadReply(0) == REDIS_OK);
1183
    config.output = original_output;
1184 1185
}

1186 1187 1188 1189
/*------------------------------------------------------------------------------
 * RDB transfer mode
 *--------------------------------------------------------------------------- */

1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214
/* This function implements --rdb, so it uses the replication protocol in order
 * to fetch the RDB file from a remote server. */
static void getRDB(void) {
    int s = context->fd;
    int fd;
    unsigned long long payload = sendSync(s);
    char buf[4096];

    fprintf(stderr,"SYNC sent to master, writing %llu bytes to '%s'\n",
        payload, config.rdb_filename);

    /* Write to file. */
    if (!strcmp(config.rdb_filename,"-")) {
        fd = STDOUT_FILENO;
    } else {
        fd = open(config.rdb_filename, O_CREAT|O_WRONLY, 0644);
        if (fd == -1) {
            fprintf(stderr, "Error opening '%s': %s\n", config.rdb_filename,
                strerror(errno));
            exit(1);
        }
    }

    while(payload) {
        ssize_t nread, nwritten;
1215

1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234
        nread = read(s,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload);
        if (nread <= 0) {
            fprintf(stderr,"I/O Error reading RDB payload from socket\n");
            exit(1);
        }
        nwritten = write(fd, buf, nread);
        if (nwritten != nread) {
            fprintf(stderr,"Error writing data to file: %s\n",
                strerror(errno));
            exit(1);
        }
        payload -= nread;
    }
    close(s); /* Close the file descriptor ASAP as fsync() may take time. */
    fsync(fd);
    fprintf(stderr,"Transfer finished with success.\n");
    exit(0);
}

1235 1236 1237 1238
/*------------------------------------------------------------------------------
 * Bulk import (pipe) mode
 *--------------------------------------------------------------------------- */

A
antirez 已提交
1239
static void pipeMode(void) {
H
Henry Rawas 已提交
1240
    int fd = (int)context->fd;
A
antirez 已提交
1241 1242 1243 1244 1245 1246 1247 1248
    long long errors = 0, replies = 0, obuf_len = 0, obuf_pos = 0;
    char ibuf[1024*16], obuf[1024*16]; /* Input and output buffers */
    char aneterr[ANET_ERR_LEN];
    redisReader *reader = redisReaderCreate();
    redisReply *reply;
    int eof = 0; /* True once we consumed all the standard input. */
    int done = 0;
    char magic[20]; /* Special reply we recognize. */
A
antirez 已提交
1249
    time_t last_read_time = time(NULL);
A
antirez 已提交
1250

A
Alexis Campailla 已提交
1251 1252 1253 1254 1255
#ifdef _WIN32
    /* Prevent translation or CRLF sequences. */
    setmode(STDIN_FILENO,_O_BINARY);
#endif

1256
    srand((unsigned int)time(NULL));
A
antirez 已提交
1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279

    /* Use non blocking I/O. */
    if (anetNonBlock(aneterr,fd) == ANET_ERR) {
        fprintf(stderr, "Can't set the socket in non blocking mode: %s\n",
            aneterr);
        exit(1);
    }

    /* Transfer raw protocol and read replies from the server at the same
     * time. */
    while(!done) {
        int mask = AE_READABLE;

        if (!eof || obuf_len != 0) mask |= AE_WRITABLE;
        mask = aeWait(fd,mask,1000);

        /* Handle the readable state: we can read replies from the server. */
        if (mask & AE_READABLE) {
            ssize_t nread;

            /* Read from socket and feed the hiredis reader. */
            do {
                nread = read(fd,ibuf,sizeof(ibuf));
1280
                if (nread == -1 && errno != EAGAIN && errno != EINTR) {
A
antirez 已提交
1281 1282 1283 1284
                    fprintf(stderr, "Error reading from the server: %s\n",
                        strerror(errno));
                    exit(1);
                }
A
antirez 已提交
1285 1286 1287 1288
                if (nread > 0) {
                    redisReaderFeed(reader,ibuf,nread);
                    last_read_time = time(NULL);
                }
A
antirez 已提交
1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302
            } while(nread > 0);

            /* Consume replies. */
            do {
                if (redisReaderGetReply(reader,(void**)&reply) == REDIS_ERR) {
                    fprintf(stderr, "Error reading replies from server\n");
                    exit(1);
                }
                if (reply) {
                    if (reply->type == REDIS_REPLY_ERROR) {
                        fprintf(stderr,"%s\n", reply->str);
                        errors++;
                    } else if (eof && reply->type == REDIS_REPLY_STRING &&
                                      reply->len == 20) {
1303
                        /* Check if this is the reply to our final ECHO
A
antirez 已提交
1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322
                         * command. If so everything was received
                         * from the server. */
                        if (memcmp(reply->str,magic,20) == 0) {
                            printf("Last reply received from server.\n");
                            done = 1;
                            replies--;
                        }
                    }
                    replies++;
                    freeReplyObject(reply);
                }
            } while(reply);
        }

        /* Handle the writable state: we can send protocol to the server. */
        if (mask & AE_WRITABLE) {
            while(1) {
                /* Transfer current buffer to server. */
                if (obuf_len != 0) {
H
Henry Rawas 已提交
1323
                    ssize_t nwritten = write(fd,obuf+obuf_pos,(unsigned int)obuf_len);
1324

A
antirez 已提交
1325
                    if (nwritten == -1) {
1326
                        if (errno != EAGAIN && errno != EINTR) {
1327 1328 1329 1330 1331 1332
                            fprintf(stderr, "Error writing to the server: %s\n",
                                strerror(errno));
                            exit(1);
                        } else {
                            nwritten = 0;
                        }
A
antirez 已提交
1333 1334 1335 1336 1337 1338 1339 1340 1341 1342
                    }
                    obuf_len -= nwritten;
                    obuf_pos += nwritten;
                    if (obuf_len != 0) break; /* Can't accept more data. */
                }
                /* If buffer is empty, load from stdin. */
                if (obuf_len == 0 && !eof) {
                    ssize_t nread = read(STDIN_FILENO,obuf,sizeof(obuf));

                    if (nread == 0) {
1343 1344 1345 1346
                        /* The ECHO sequence starts with a "\r\n" so that if there
                         * is garbage in the protocol we read from stdin, the ECHO
                         * will likely still be properly formatted.
                         * CRLF is ignored by Redis, so it has no effects. */
A
antirez 已提交
1347
                        char echo[] =
1348
                        "\r\n*2\r\n$4\r\nECHO\r\n$20\r\n01234567890123456789\r\n";
A
antirez 已提交
1349 1350 1351
                        int j;

                        eof = 1;
G
guiquanz 已提交
1352
                        /* Everything transferred, so we queue a special
A
antirez 已提交
1353 1354 1355 1356
                         * ECHO command that we can match in the replies
                         * to make sure everything was read from the server. */
                        for (j = 0; j < 20; j++)
                            magic[j] = rand() & 0xff;
1357
                        memcpy(echo+21,magic,20);
A
antirez 已提交
1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373
                        memcpy(obuf,echo,sizeof(echo)-1);
                        obuf_len = sizeof(echo)-1;
                        obuf_pos = 0;
                        printf("All data transferred. Waiting for the last reply...\n");
                    } else if (nread == -1) {
                        fprintf(stderr, "Error reading from stdin: %s\n",
                            strerror(errno));
                        exit(1);
                    } else {
                        obuf_len = nread;
                        obuf_pos = 0;
                    }
                }
                if (obuf_len == 0 && eof) break;
            }
        }
A
antirez 已提交
1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385

        /* Handle timeout, that is, we reached EOF, and we are not getting
         * replies from the server for a few seconds, nor the final ECHO is
         * received. */
        if (eof && config.pipe_timeout > 0 &&
            time(NULL)-last_read_time > config.pipe_timeout)
        {
            fprintf(stderr,"No replies for %d seconds: exiting.\n",
                config.pipe_timeout);
            errors++;
            break;
        }
A
antirez 已提交
1386 1387 1388 1389 1390 1391 1392 1393 1394
    }
    redisReaderFree(reader);
    printf("errors: %lld, replies: %lld\n", errors, replies);
    if (errors)
        exit(1);
    else
        exit(0);
}

1395 1396 1397 1398
/*------------------------------------------------------------------------------
 * Find big keys
 *--------------------------------------------------------------------------- */

A
antirez 已提交
1399 1400 1401 1402 1403
#define TYPE_STRING 0
#define TYPE_LIST   1
#define TYPE_SET    2
#define TYPE_HASH   3
#define TYPE_ZSET   4
1404
#define TYPE_NONE   5
A
antirez 已提交
1405

1406 1407
static redisReply *sendScan(unsigned long long *it) {
    redisReply *reply = redisCommand(context, "SCAN %llu", *it);
A
antirez 已提交
1408

1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422
    /* Handle any error conditions */
    if(reply == NULL) {
        fprintf(stderr, "\nI/O error\n");
        exit(1);
    } else if(reply->type == REDIS_REPLY_ERROR) {
        fprintf(stderr, "SCAN error: %s\n", reply->str);
        exit(1);
    } else if(reply->type != REDIS_REPLY_ARRAY) {
        fprintf(stderr, "Non ARRAY response from SCAN!\n");
        exit(1);
    } else if(reply->elements != 2) {
        fprintf(stderr, "Invalid element count from SCAN!\n");
        exit(1);
    }
M
michael-grunder 已提交
1423

1424 1425 1426
    /* Validate our types are correct */
    assert(reply->element[0]->type == REDIS_REPLY_STRING);
    assert(reply->element[1]->type == REDIS_REPLY_ARRAY);
1427

1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438
    /* Update iterator */
    *it = atoi(reply->element[0]->str);

    return reply;
}

static int getDbSize(void) {
    redisReply *reply;
    int size;

    reply = redisCommand(context, "DBSIZE");
1439

1440 1441 1442 1443 1444 1445
    if(reply == NULL || reply->type != REDIS_REPLY_INTEGER) {
        fprintf(stderr, "Couldn't determine DBSIZE!\n");
        exit(1);
    }

    /* Grab the number of keys and free our reply */
1446
    size = (int)reply->integer;
1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472
    freeReplyObject(reply);

    return size;
}

static int toIntType(char *key, char *type) {
    if(!strcmp(type, "string")) {
        return TYPE_STRING;
    } else if(!strcmp(type, "list")) {
        return TYPE_LIST;
    } else if(!strcmp(type, "set")) {
        return TYPE_SET;
    } else if(!strcmp(type, "hash")) {
        return TYPE_HASH;
    } else if(!strcmp(type, "zset")) {
        return TYPE_ZSET;
    } else if(!strcmp(type, "none")) {
        return TYPE_NONE;
    } else {
        fprintf(stderr, "Unknown type '%s' for key '%s'\n", type, key);
        exit(1);
    }
}

static void getKeyTypes(redisReply *keys, int *types) {
    redisReply *reply;
1473
    unsigned int i;
1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484

    /* Pipeline TYPE commands */
    for(i=0;i<keys->elements;i++) {
        redisAppendCommand(context, "TYPE %s", keys->element[i]->str);
    }

    /* Retrieve types */
    for(i=0;i<keys->elements;i++) {
        if(redisGetReply(context, (void**)&reply)!=REDIS_OK) {
            fprintf(stderr, "Error getting type for key '%s' (%d: %s)\n",
                keys->element[i]->str, context->err, context->errstr);
A
antirez 已提交
1485
            exit(1);
1486 1487 1488
        } else if(reply->type != REDIS_REPLY_STATUS) {
            fprintf(stderr, "Invalid reply type (%d) for TYPE on key '%s'!\n",
                reply->type, keys->element[i]->str);
1489
            exit(1);
A
antirez 已提交
1490
        }
1491

1492
        types[i] = toIntType(keys->element[i]->str, reply->str);
1493 1494 1495 1496
        freeReplyObject(reply);
    }
}

1497 1498
static void getKeySizes(redisReply *keys, int *types,
                        unsigned long long *sizes)
1499 1500 1501
{
    redisReply *reply;
    char *sizecmds[] = {"STRLEN","LLEN","SCARD","HLEN","ZCARD"};
1502
    unsigned int i;
1503 1504 1505 1506

    /* Pipeline size commands */
    for(i=0;i<keys->elements;i++) {
        /* Skip keys that were deleted */
1507
        if(types[i]==TYPE_NONE)
A
antirez 已提交
1508
            continue;
1509

1510
        redisAppendCommand(context, "%s %s", sizecmds[types[i]],
1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525
            keys->element[i]->str);
    }

    /* Retreive sizes */
    for(i=0;i<keys->elements;i++) {
        /* Skip keys that dissapeared between SCAN and TYPE */
        if(types[i] == TYPE_NONE) {
            sizes[i] = 0;
            continue;
        }

        /* Retreive size */
        if(redisGetReply(context, (void**)&reply)!=REDIS_OK) {
            fprintf(stderr, "Error getting size for key '%s' (%d: %s)\n",
                keys->element[i]->str, context->err, context->errstr);
1526
            exit(1);
1527 1528 1529
        } else if(reply->type != REDIS_REPLY_INTEGER) {
            /* Theoretically the key could have been removed and
             * added as a different type between TYPE and SIZE */
1530
            fprintf(stderr,
1531 1532 1533
                "Warning:  %s on '%s' failed (may have changed type)\n",
                 sizecmds[types[i]], keys->element[i]->str);
            sizes[i] = 0;
A
antirez 已提交
1534
        } else {
1535 1536
            sizes[i] = reply->integer;
        }
1537

1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548
        freeReplyObject(reply);
    }
}

static void findBigKeys(void) {
    unsigned long long biggest[5] = {0}, counts[5] = {0}, totalsize[5] = {0};
    unsigned long long sampled = 0, total_keys, totlen=0, *sizes=NULL, it=0;
    sds maxkeys[5] = {0};
    char *typename[] = {"string","list","set","hash","zset"};
    char *typeunit[] = {"bytes","items","members","fields","members"};
    redisReply *reply, *keys;
1549 1550
    unsigned int arrsize=0, i;
    int type, *types=NULL;
1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564
    double pct;

    /* Total keys pre scanning */
    total_keys = getDbSize();

    /* Status message */
    printf("\n# Scanning the entire keyspace to find biggest keys as well as\n");
    printf("# average sizes per key type.  You can use -i 0.1 to sleep 0.1 sec\n");
    printf("# per 100 SCAN commands (not usually needed).\n\n");

    /* New up sds strings to keep track of overall biggest per type */
    for(i=0;i<TYPE_NONE; i++) {
        maxkeys[i] = sdsempty();
        if(!maxkeys[i]) {
1565
            fprintf(stderr, "Failed to allocate memory for largest key names!\n");
A
antirez 已提交
1566 1567
            exit(1);
        }
1568
    }
A
antirez 已提交
1569

1570 1571 1572 1573
    /* SCAN loop */
    do {
        /* Calculate approximate percentage completion */
        pct = 100 * (double)sampled/total_keys;
M
michael-grunder 已提交
1574

1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585
        /* Grab some keys and point to the keys array */
        reply = sendScan(&it);
        keys  = reply->element[1];

        /* Reallocate our type and size array if we need to */
        if(keys->elements > arrsize) {
            types = zrealloc(types, sizeof(int)*keys->elements);
            sizes = zrealloc(sizes, sizeof(unsigned long long)*keys->elements);

            if(!types || !sizes) {
                fprintf(stderr, "Failed to allocate storage for keys!\n");
M
michael-grunder 已提交
1586
                exit(1);
A
antirez 已提交
1587
            }
1588

1589
            arrsize = (int)keys->elements;
A
antirez 已提交
1590 1591
        }

1592 1593 1594
        /* Retreive types and then sizes */
        getKeyTypes(keys, types);
        getKeySizes(keys, types, sizes);
1595

1596 1597 1598 1599
        /* Now update our stats */
        for(i=0;i<keys->elements;i++) {
            if((type = types[i]) == TYPE_NONE)
                continue;
1600

1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616
            totalsize[type] += sizes[i];
            counts[type]++;
            totlen += keys->element[i]->len;
            sampled++;

            if(biggest[type]<sizes[i]) {
                printf(
                   "[%05.2f%%] Biggest %-6s found so far '%s' with %llu %s\n",
                   pct, typename[type], keys->element[i]->str, sizes[i],
                   typeunit[type]);

                /* Keep track of biggest key name for this type */
                maxkeys[type] = sdscpy(maxkeys[type], keys->element[i]->str);
                if(!maxkeys[type]) {
                    fprintf(stderr, "Failed to allocate memory for key!\n");
                    exit(1);
M
michael-grunder 已提交
1617
                }
1618 1619

                /* Keep track of the biggest size for this type */
1620
                biggest[type] = sizes[i];
A
antirez 已提交
1621
            }
M
michael-grunder 已提交
1622

1623 1624 1625 1626
            /* Update overall progress */
            if(sampled % 1000000 == 0) {
                printf("[%05.2f%%] Sampled %llu keys so far\n", pct, sampled);
            }
A
antirez 已提交
1627 1628
        }

1629 1630
        /* Sleep if we've been directed to do so */
        if(sampled && (sampled %100) == 0 && config.interval) {
A
antirez 已提交
1631
            usleep(config.interval);
1632
        }
1633

1634
        freeReplyObject(reply);
M
michael-grunder 已提交
1635 1636
    } while(it != 0);

1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655
    if(types) zfree(types);
    if(sizes) zfree(sizes);

    /* We're done */
    printf("\n-------- summary -------\n\n");

    printf("Sampled %llu keys in the keyspace!\n", sampled);
    printf("Total key length in bytes is %llu (avg len %.2f)\n\n",
       totlen, totlen ? (double)totlen/sampled : 0);

    /* Output the biggest keys we found, for types we did find */
    for(i=0;i<TYPE_NONE;i++) {
        if(sdslen(maxkeys[i])>0) {
            printf("Biggest %6s found '%s' has %llu %s\n", typename[i], maxkeys[i],
               biggest[i], typeunit[i]);
        }
    }

    printf("\n");
A
antirez 已提交
1656

1657 1658 1659 1660 1661
    for(i=0;i<TYPE_NONE;i++) {
        printf("%llu %ss with %llu %s (%05.2f%% of keys, avg size %.2f)\n",
           counts[i], typename[i], totalsize[i], typeunit[i],
           sampled ? 100 * (double)counts[i]/sampled : 0,
           counts[i] ? (double)totalsize[i]/counts[i] : 0);
A
antirez 已提交
1662
    }
1663 1664 1665 1666 1667 1668 1669

    /* Free sds strings containing max keys */
    for(i=0;i<TYPE_NONE;i++) {
        sdsfree(maxkeys[i]);
    }

    /* Success! */
M
michael-grunder 已提交
1670
    exit(0);
A
antirez 已提交
1671 1672
}

1673 1674 1675 1676
/*------------------------------------------------------------------------------
 * Stats mode
 *--------------------------------------------------------------------------- */

1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733
/* Return the specified INFO field from the INFO command output "info".
 * A new buffer is allocated for the result, that needs to be free'd.
 * If the field is not found NULL is returned. */
static char *getInfoField(char *info, char *field) {
    char *p = strstr(info,field);
    char *n1, *n2;
    char *result;

    if (!p) return NULL;
    p += strlen(field)+1;
    n1 = strchr(p,'\r');
    n2 = strchr(p,',');
    if (n2 && n2 < n1) n1 = n2;
    result = malloc(sizeof(char)*(n1-p)+1);
    memcpy(result,p,(n1-p));
    result[n1-p] = '\0';
    return result;
}

/* Like the above function but automatically convert the result into
 * a long. On error (missing field) LONG_MIN is returned. */
static long getLongInfoField(char *info, char *field) {
    char *value = getInfoField(info,field);
    long l;

    if (!value) return LONG_MIN;
    l = strtol(value,NULL,10);
    free(value);
    return l;
}

/* Convert number of bytes into a human readable string of the form:
 * 100B, 2G, 100M, 4K, and so forth. */
void bytesToHuman(char *s, long long n) {
    double d;

    if (n < 0) {
        *s = '-';
        s++;
        n = -n;
    }
    if (n < 1024) {
        /* Bytes */
        sprintf(s,"%lluB",n);
        return;
    } else if (n < (1024*1024)) {
        d = (double)n/(1024);
        sprintf(s,"%.2fK",d);
    } else if (n < (1024LL*1024*1024)) {
        d = (double)n/(1024*1024);
        sprintf(s,"%.2fM",d);
    } else if (n < (1024LL*1024*1024*1024)) {
        d = (double)n/(1024LL*1024*1024);
        sprintf(s,"%.2fG",d);
    }
}

1734
static void statMode(void) {
1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815
    redisReply *reply;
    long aux, requests = 0;
    int i = 0;

    while(1) {
        char buf[64];
        int j;

        reply = reconnectingInfo();
        if (reply->type == REDIS_REPLY_ERROR) {
            printf("ERROR: %s\n", reply->str);
            exit(1);
        }

        if ((i++ % 20) == 0) {
            printf(
"------- data ------ --------------------- load -------------------- - child -\n"
"keys       mem      clients blocked requests            connections          \n");
        }

        /* Keys */
        aux = 0;
        for (j = 0; j < 20; j++) {
            long k;

            sprintf(buf,"db%d:keys",j);
            k = getLongInfoField(reply->str,buf);
            if (k == LONG_MIN) continue;
            aux += k;
        }
        sprintf(buf,"%ld",aux);
        printf("%-11s",buf);

        /* Used memory */
        aux = getLongInfoField(reply->str,"used_memory");
        bytesToHuman(buf,aux);
        printf("%-8s",buf);

        /* Clients */
        aux = getLongInfoField(reply->str,"connected_clients");
        sprintf(buf,"%ld",aux);
        printf(" %-8s",buf);

        /* Blocked (BLPOPPING) Clients */
        aux = getLongInfoField(reply->str,"blocked_clients");
        sprintf(buf,"%ld",aux);
        printf("%-8s",buf);

        /* Requets */
        aux = getLongInfoField(reply->str,"total_commands_processed");
        sprintf(buf,"%ld (+%ld)",aux,requests == 0 ? 0 : aux-requests);
        printf("%-19s",buf);
        requests = aux;

        /* Connections */
        aux = getLongInfoField(reply->str,"total_connections_received");
        sprintf(buf,"%ld",aux);
        printf(" %-12s",buf);

        /* Children */
        aux = getLongInfoField(reply->str,"bgsave_in_progress");
        aux |= getLongInfoField(reply->str,"aof_rewrite_in_progress") << 1;
        switch(aux) {
        case 0: break;
        case 1:
            printf("SAVE");
            break;
        case 2:
            printf("AOF");
            break;
        case 3:
            printf("SAVE+AOF");
            break;
        }

        printf("\n");
        freeReplyObject(reply);
        usleep(config.interval);
    }
}

1816 1817 1818 1819
/*------------------------------------------------------------------------------
 * Scan mode
 *--------------------------------------------------------------------------- */

1820
static void scanMode(void) {
A
antirez 已提交
1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836
    redisReply *reply;
    unsigned long long cur = 0;

    do {
        if (config.pattern)
            reply = redisCommand(context,"SCAN %llu MATCH %s",
                cur,config.pattern);
        else
            reply = redisCommand(context,"SCAN %llu",cur);
        if (reply == NULL) {
            printf("I/O error\n");
            exit(1);
        } else if (reply->type == REDIS_REPLY_ERROR) {
            printf("ERROR: %s\n", reply->str);
            exit(1);
        } else {
1837
            unsigned int j;
A
antirez 已提交
1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848

            cur = strtoull(reply->element[0]->str,NULL,10);
            for (j = 0; j < reply->element[1]->elements; j++)
                printf("%s\n", reply->element[1]->element[j]->str);
        }
        freeReplyObject(reply);
    } while(cur != 0);

    exit(0);
}

1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859
/*------------------------------------------------------------------------------
 * Intrisic latency mode.
 *
 * Measure max latency of a running process that does not result from
 * syscalls. Basically this software should provide an hint about how much
 * time the kernel leaves the process without a chance to run.
 *--------------------------------------------------------------------------- */

/* This is just some computation the compiler can't optimize out.
 * Should run in less than 100-200 microseconds even using very
 * slow hardware. Runs in less than 10 microseconds in modern HW. */
1860
unsigned long compute_something_fast(void) {
1861
    unsigned char s[256], i, j, t;
1862
    int count = 1000, k;
1863
    unsigned long output = 0;
1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879

    for (k = 0; k < 256; k++) s[k] = k;

    i = 0;
    j = 0;
    while(count--) {
        i++;
        j = j + s[i];
        t = s[i];
        s[i] = s[j];
        s[j] = t;
        output += s[(s[i]+s[j])&255];
    }
    return output;
}

1880 1881 1882 1883 1884
static void intrinsicLatencyModeStop(int s) {
    REDIS_NOTUSED(s);
    force_cancel_loop = 1;
}

1885 1886 1887 1888 1889
static void intrinsicLatencyMode(void) {
    long long test_end, run_time, max_latency = 0, runs = 0;

    run_time = config.intrinsic_latency_duration*1000000;
    test_end = ustime() + run_time;
1890
    signal(SIGINT, intrinsicLatencyModeStop);
1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907

    while(1) {
        long long start, end, latency;

        start = ustime();
        compute_something_fast();
        end = ustime();
        latency = end-start;
        runs++;
        if (latency <= 0) continue;

        /* Reporting */
        if (latency > max_latency) {
            max_latency = latency;
            printf("Max latency so far: %lld microseconds.\n", max_latency);
        }

1908 1909
        double avg_us = (double)run_time/runs;
        double avg_ns = avg_us * 10e3;
1910
        if (force_cancel_loop || end > test_end) {
1911 1912 1913 1914 1915 1916
            printf("\n%lld total runs "
                "(avg latency: "
                "%.4f microseconds / %.2f nanoseconds per run).\n",
                runs, avg_us, avg_ns);
            printf("Worst run took %.0fx longer than the average latency.\n",
                max_latency / avg_us);
1917 1918 1919 1920 1921 1922 1923 1924 1925
            exit(0);
        }
    }
}

/*------------------------------------------------------------------------------
 * Program main()
 *--------------------------------------------------------------------------- */

A
antirez 已提交
1926
int main(int argc, char **argv) {
1927
    int firstarg;
A
antirez 已提交
1928

A
antirez 已提交
1929
    config.hostip = sdsnew("127.0.0.1");
A
antirez 已提交
1930
    config.hostport = 6379;
1931
    config.hostsocket = NULL;
1932
    config.repeat = 1;
1933
    config.interval = 0;
I
ian 已提交
1934
    config.dbnum = 0;
1935
    config.interactive = 0;
1936
    config.shutdown = 0;
1937 1938
    config.monitor_mode = 0;
    config.pubsub_mode = 0;
A
antirez 已提交
1939
    config.latency_mode = 0;
1940
    config.latency_history = 0;
1941
    config.cluster_mode = 0;
A
antirez 已提交
1942
    config.slave_mode = 0;
1943
    config.getrdb_mode = 0;
A
antirez 已提交
1944 1945
    config.stat_mode = 0;
    config.scan_mode = 0;
1946
    config.intrinsic_latency_mode = 0;
A
antirez 已提交
1947
    config.pattern = NULL;
1948
    config.rdb_filename = NULL;
A
antirez 已提交
1949
    config.pipe_mode = 0;
1950
    config.pipe_timeout = REDIS_CLI_DEFAULT_PIPE_TIMEOUT;
A
antirez 已提交
1951
    config.bigkeys = 0;
1952
    config.stdinarg = 0;
A
antirez 已提交
1953
    config.auth = NULL;
A
antirez 已提交
1954
    config.eval = NULL;
1955 1956
    config.last_cmd_type = -1;

1957 1958 1959 1960
    if (!isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL))
        config.output = OUTPUT_RAW;
    else
        config.output = OUTPUT_STANDARD;
P
Pieter Noordhuis 已提交
1961
    config.mb_delim = sdsnew("\n");
1962
    cliInitHelp();
1963

A
antirez 已提交
1964 1965 1966 1967
    firstarg = parseOptions(argc,argv);
    argc -= firstarg;
    argv += firstarg;

A
antirez 已提交
1968
    /* Latency mode */
A
antirez 已提交
1969
    if (config.latency_mode) {
1970
        if (cliConnect(0) == REDIS_ERR) exit(1);
A
antirez 已提交
1971 1972 1973
        latencyMode();
    }

A
antirez 已提交
1974
    /* Slave mode */
1975
    if (config.slave_mode) {
1976
        if (cliConnect(0) == REDIS_ERR) exit(1);
1977 1978 1979
        slaveMode();
    }

1980 1981
    /* Get RDB mode. */
    if (config.getrdb_mode) {
1982
        if (cliConnect(0) == REDIS_ERR) exit(1);
1983 1984 1985
        getRDB();
    }

A
antirez 已提交
1986 1987
    /* Pipe mode */
    if (config.pipe_mode) {
1988
        if (cliConnect(0) == REDIS_ERR) exit(1);
A
antirez 已提交
1989 1990 1991
        pipeMode();
    }

A
antirez 已提交
1992 1993
    /* Find big keys */
    if (config.bigkeys) {
1994
        if (cliConnect(0) == REDIS_ERR) exit(1);
A
antirez 已提交
1995 1996 1997
        findBigKeys();
    }

1998 1999 2000 2001 2002 2003 2004
    /* Stat mode */
    if (config.stat_mode) {
        if (cliConnect(0) == REDIS_ERR) exit(1);
        if (config.interval == 0) config.interval = 1000000;
        statMode();
    }

A
antirez 已提交
2005 2006 2007 2008 2009 2010
    /* Scan mode */
    if (config.scan_mode) {
        if (cliConnect(0) == REDIS_ERR) exit(1);
        scanMode();
    }

2011 2012 2013
    /* Intrinsic latency mode */
    if (config.intrinsic_latency_mode) intrinsicLatencyMode();

2014
    /* Start interactive mode when no command is provided */
A
antirez 已提交
2015
    if (argc == 0 && !config.eval) {
2016 2017 2018
        /* Ignore SIGPIPE in interactive mode to force a reconnect */
        signal(SIGPIPE, SIG_IGN);

2019 2020 2021 2022 2023 2024
        /* Note that in repl mode we don't abort on connection error.
         * A new attempt will be performed for every command send. */
        cliConnect(0);
        repl();
    }

2025
    /* Otherwise, we have some arguments to execute */
2026
    if (cliConnect(0) != REDIS_OK) exit(1);
A
antirez 已提交
2027 2028 2029 2030 2031
    if (config.eval) {
        return evalMode(argc,argv);
    } else {
        return noninteractive(argc,convertToSds(argc,argv));
    }
A
antirez 已提交
2032
}