提交 d4f37363 编写于 作者: A antirez

Merge branch 'testing' into 3.0

......@@ -44,6 +44,7 @@
#include <assert.h>
#include <fcntl.h>
#include <limits.h>
#include <math.h>
#include "hiredis.h"
#include "sds.h"
......@@ -60,6 +61,8 @@
#define OUTPUT_CSV 2
#define REDIS_CLI_KEEPALIVE_INTERVAL 15 /* seconds */
#define REDIS_CLI_DEFAULT_PIPE_TIMEOUT 30 /* seconds */
#define REDIS_CLI_HISTFILE_ENV "REDISCLI_HISTFILE"
#define REDIS_CLI_HISTFILE_DEFAULT ".rediscli_history"
static redisContext *context;
static struct config {
......@@ -74,7 +77,10 @@ static struct config {
int monitor_mode;
int pubsub_mode;
int latency_mode;
int latency_dist_mode;
int latency_history;
int lru_test_mode;
long long lru_test_sample_size;
int cluster_mode;
int cluster_reissue_command;
int slave_mode;
......@@ -138,6 +144,30 @@ static void cliRefreshPrompt(void) {
snprintf(config.prompt+len,sizeof(config.prompt)-len,"> ");
}
static sds getHistoryPath() {
char *path = NULL;
sds historyPath = NULL;
/* check the env for a histfile override */
path = getenv(REDIS_CLI_HISTFILE_ENV);
if (path != NULL && *path != '\0') {
if (!strcmp("/dev/null", path)) {
return NULL;
}
/* if the env is set, return it */
historyPath = sdscatprintf(sdsempty(), "%s", path);
} else {
char *home = getenv("HOME");
if (home != NULL && *home != '\0') {
/* otherwise, return the default */
historyPath = sdscatprintf(sdsempty(), "%s/%s", home, REDIS_CLI_HISTFILE_DEFAULT);
}
}
return historyPath;
}
/*------------------------------------------------------------------------------
* Help functions
*--------------------------------------------------------------------------- */
......@@ -672,16 +702,17 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
return REDIS_OK;
}
/* Send the INFO command, reconnecting the link if needed. */
static redisReply *reconnectingInfo(void) {
redisContext *c = context;
/* Send a command reconnecting the link if needed. */
static redisReply *reconnectingRedisCommand(redisContext *c, const char *fmt, ...) {
redisReply *reply = NULL;
int tries = 0;
va_list ap;
assert(!c->err);
while(reply == NULL) {
while (c->err & (REDIS_ERR_IO | REDIS_ERR_EOF)) {
printf("Reconnecting (%d)...\r", ++tries);
printf("\r\x1b[0K"); /* Cursor to left edge + clear line. */
printf("Reconnecting... %d\r", ++tries);
fflush(stdout);
redisFree(c);
......@@ -689,12 +720,15 @@ static redisReply *reconnectingInfo(void) {
usleep(1000000);
}
reply = redisCommand(c,"INFO");
va_start(ap,fmt);
reply = redisvCommand(c,fmt,ap);
va_end(ap);
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");
printf("\r\x1b[0K"); /* Cursor to left edge + clear line. */
}
}
......@@ -742,9 +776,14 @@ static int parseOptions(int argc, char **argv) {
config.output = OUTPUT_CSV;
} else if (!strcmp(argv[i],"--latency")) {
config.latency_mode = 1;
} else if (!strcmp(argv[i],"--latency-dist")) {
config.latency_dist_mode = 1;
} else if (!strcmp(argv[i],"--latency-history")) {
config.latency_mode = 1;
config.latency_history = 1;
} else if (!strcmp(argv[i],"--lru-test") && !lastarg) {
config.lru_test_mode = 1;
config.lru_test_sample_size = strtoll(argv[++i],NULL,10);
} else if (!strcmp(argv[i],"--slave")) {
config.slave_mode = 1;
} else if (!strcmp(argv[i],"--stat")) {
......@@ -834,6 +873,9 @@ static void usage(void) {
" --latency Enter a special mode continuously sampling latency.\n"
" --latency-history Like --latency but tracking latency changes over time.\n"
" Default time interval is 15 sec. Change it using -i.\n"
" --latency-dist Shows latency as a spectrum, requires xterm 256 colors.\n"
" Default time interval is 1 sec. Change it using -i.\n"
" --lru-test <keys> Simulate a cache workload with an 80-20 distribution.\n"
" --slave Simulate a slave showing commands received from the master.\n"
" --rdb <filename> Transfer an RDB dump from remote server to local file.\n"
" --pipe Transfer raw Redis protocol from stdin to server.\n"
......@@ -918,10 +960,9 @@ static void repl(void) {
/* Only use history when stdin is a tty. */
if (isatty(fileno(stdin))) {
history = 1;
if (getenv("HOME") != NULL) {
historyfile = sdscatprintf(sdsempty(),"%s/.rediscli_history",getenv("HOME"));
historyfile = getHistoryPath();
if (historyfile != NULL) {
history = 1;
linenoiseHistoryLoad(historyfile);
}
}
......@@ -1050,7 +1091,7 @@ static void latencyMode(void) {
if (!context) exit(1);
while(1) {
start = mstime();
reply = redisCommand(context,"PING");
reply = reconnectingRedisCommand(context,"PING");
if (reply == NULL) {
fprintf(stderr,"\nI/O error\n");
exit(1);
......@@ -1080,6 +1121,155 @@ static void latencyMode(void) {
}
}
/*------------------------------------------------------------------------------
* Latency distribution mode -- requires 256 colors xterm
*--------------------------------------------------------------------------- */
#define LATENCY_DIST_DEFAULT_INTERVAL 1000 /* milliseconds. */
#define LATENCY_DIST_MIN_GRAY 233 /* Less than that is too hard to see gray. */
#define LATENCY_DIST_MAX_GRAY 255
#define LATENCY_DIST_GRAYS (LATENCY_DIST_MAX_GRAY-LATENCY_DIST_MIN_GRAY+1)
/* Gray palette. */
int spectrum_palette_size = 24;
int spectrum_palette[] = {0, 233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255};
/* Structure to store samples distribution. */
struct distsamples {
long long max; /* Max latency to fit into this interval (usec). */
long long count; /* Number of samples in this interval. */
int character; /* Associated character in visualization. */
};
/* Helper function for latencyDistMode(). Performs the spectrum visualization
* of the collected samples targeting an xterm 256 terminal.
*
* Takes an array of distsamples structures, ordered from smaller to bigger
* 'max' value. Last sample max must be 0, to mean that it olds all the
* samples greater than the previous one, and is also the stop sentinel.
*
* "tot' is the total number of samples in the different buckets, so it
* is the SUM(samples[i].conut) for i to 0 up to the max sample.
*
* As a side effect the function sets all the buckets count to 0. */
void showLatencyDistSamples(struct distsamples *samples, long long tot) {
int j;
/* We convert samples into a index inside the palette
* proportional to the percentage a given bucket represents.
* This way intensity of the different parts of the spectrum
* don't change relative to the number of requests, which avoids to
* pollute the visualization with non-latency related info. */
printf("\033[38;5;0m"); /* Set foreground color to black. */
for (j = 0; ; j++) {
int coloridx =
ceil((float) samples[j].count / tot * (spectrum_palette_size-1));
int color = spectrum_palette[coloridx];
printf("\033[48;5;%dm%c", (int)color, samples[j].character);
samples[j].count = 0;
if (samples[j].max == 0) break; /* Last sample. */
}
printf("\033[0m\n");
fflush(stdout);
}
/* Show the legend: different buckets values and colors meaning, so
* that the spectrum is more easily readable. */
void showLatencyDistLegend(void) {
int j;
printf("---------------------------------------------\n");
printf(". - * # .01 .125 .25 .5 milliseconds\n");
printf("1,2,3,...,9 from 1 to 9 milliseconds\n");
printf("A,B,C,D,E 10,20,30,40,50 milliseconds\n");
printf("F,G,H,I,J .1,.2,.3,.4,.5 seconds\n");
printf("K,L,M,N,O,P,Q,? 1,2,4,8,16,30,60,>60 seconds\n");
printf("From 0 to 100%%: ");
for (j = 0; j < spectrum_palette_size; j++) {
printf("\033[48;5;%dm ", spectrum_palette[j]);
}
printf("\033[0m\n");
printf("---------------------------------------------\n");
}
static void latencyDistMode(void) {
redisReply *reply;
long long start, latency, count = 0;
long long history_interval =
config.interval ? config.interval/1000 :
LATENCY_DIST_DEFAULT_INTERVAL;
long long history_start = ustime();
int j, outputs = 0;
struct distsamples samples[] = {
/* We use a mostly logarithmic scale, with certain linear intervals
* which are more interesting than others, like 1-10 milliseconds
* range. */
{10,0,'.'}, /* 0.01 ms */
{125,0,'-'}, /* 0.125 ms */
{250,0,'*'}, /* 0.25 ms */
{500,0,'#'}, /* 0.5 ms */
{1000,0,'1'}, /* 1 ms */
{2000,0,'2'}, /* 2 ms */
{3000,0,'3'}, /* 3 ms */
{4000,0,'4'}, /* 4 ms */
{5000,0,'5'}, /* 5 ms */
{6000,0,'6'}, /* 6 ms */
{7000,0,'7'}, /* 7 ms */
{8000,0,'8'}, /* 8 ms */
{9000,0,'9'}, /* 9 ms */
{10000,0,'A'}, /* 10 ms */
{20000,0,'B'}, /* 20 ms */
{30000,0,'C'}, /* 30 ms */
{40000,0,'D'}, /* 40 ms */
{50000,0,'E'}, /* 50 ms */
{100000,0,'F'}, /* 0.1 s */
{200000,0,'G'}, /* 0.2 s */
{300000,0,'H'}, /* 0.3 s */
{400000,0,'I'}, /* 0.4 s */
{500000,0,'J'}, /* 0.5 s */
{1000000,0,'K'}, /* 1 s */
{2000000,0,'L'}, /* 2 s */
{4000000,0,'M'}, /* 4 s */
{8000000,0,'N'}, /* 8 s */
{16000000,0,'O'}, /* 16 s */
{30000000,0,'P'}, /* 30 s */
{60000000,0,'Q'}, /* 1 minute */
{0,0,'?'}, /* > 1 minute */
};
if (!context) exit(1);
while(1) {
start = ustime();
reply = reconnectingRedisCommand(context,"PING");
if (reply == NULL) {
fprintf(stderr,"\nI/O error\n");
exit(1);
}
latency = ustime()-start;
freeReplyObject(reply);
count++;
/* Populate the relevant bucket. */
for (j = 0; ; j++) {
if (samples[j].max == 0 || latency <= samples[j].max) {
samples[j].count++;
break;
}
}
/* From time to time show the spectrum. */
if (count && (ustime()-history_start)/1000 > history_interval) {
if ((outputs++ % 20) == 0)
showLatencyDistLegend();
showLatencyDistSamples(samples,count);
history_start = ustime();
count = 0;
}
usleep(LATENCY_SAMPLE_RATE * 1000);
}
}
/*------------------------------------------------------------------------------
* Slave mode
*--------------------------------------------------------------------------- */
......@@ -1699,7 +1889,7 @@ static void statMode(void) {
char buf[64];
int j;
reply = reconnectingInfo();
reply = reconnectingRedisCommand(context,"INFO");
if (reply->type == REDIS_REPLY_ERROR) {
printf("ERROR: %s\n", reply->str);
exit(1);
......@@ -1805,6 +1995,94 @@ static void scanMode(void) {
exit(0);
}
/*------------------------------------------------------------------------------
* LRU test mode
*--------------------------------------------------------------------------- */
/* Return an integer from min to max (both inclusive) using a power-law
* distribution, depending on the value of alpha: the greater the alpha
* the more bias towards lower values.
*
* With alpha = 6.2 the output follows the 80-20 rule where 20% of
* the returned numbers will account for 80% of the frequency. */
long long powerLawRand(long long min, long long max, double alpha) {
double pl, r;
max += 1;
r = ((double)rand()) / RAND_MAX;
pl = pow(
((pow(max,alpha+1) - pow(min,alpha+1))*r + pow(min,alpha+1)),
(1.0/(alpha+1)));
return (max-1-(long long)pl)+min;
}
/* Generates a key name among a set of lru_test_sample_size keys, using
* an 80-20 distribution. */
void LRUTestGenKey(char *buf, size_t buflen) {
snprintf(buf, buflen, "lru:%lld\n",
powerLawRand(1, config.lru_test_sample_size, 6.2));
}
#define LRU_CYCLE_PERIOD 1000 /* 1000 milliseconds. */
#define LRU_CYCLE_PIPELINE_SIZE 250
static void LRUTestMode(void) {
redisReply *reply;
char key[128];
long long start_cycle;
int j;
srand(time(NULL)^getpid());
while(1) {
/* Perform cycles of 1 second with 50% writes and 50% reads.
* We use pipelining batching writes / reads N times per cycle in order
* to fill the target instance easily. */
start_cycle = mstime();
long long hits = 0, misses = 0;
while(mstime() - start_cycle < 1000) {
/* Write cycle. */
for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) {
LRUTestGenKey(key,sizeof(key));
redisAppendCommand(context, "SET %s val",key);
}
for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++)
redisGetReply(context, (void**)&reply);
/* Read cycle. */
for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) {
LRUTestGenKey(key,sizeof(key));
redisAppendCommand(context, "GET %s",key);
}
for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) {
if (redisGetReply(context, (void**)&reply) == REDIS_OK) {
switch(reply->type) {
case REDIS_REPLY_ERROR:
printf("%s\n", reply->str);
break;
case REDIS_REPLY_NIL:
misses++;
break;
default:
hits++;
break;
}
}
}
if (context->err) {
fprintf(stderr,"I/O error during LRU test\n");
exit(1);
}
}
/* Print stats. */
printf(
"%lld Gets/sec | Hits: %lld (%.2f%%) | Misses: %lld (%.2f%%)\n",
hits+misses,
hits, (double)hits/(hits+misses)*100,
misses, (double)misses/(hits+misses)*100);
}
exit(0);
}
/*------------------------------------------------------------------------------
* Intrisic latency mode.
*
......@@ -1896,7 +2174,10 @@ int main(int argc, char **argv) {
config.monitor_mode = 0;
config.pubsub_mode = 0;
config.latency_mode = 0;
config.latency_dist_mode = 0;
config.latency_history = 0;
config.lru_test_mode = 0;
config.lru_test_sample_size = 0;
config.cluster_mode = 0;
config.slave_mode = 0;
config.getrdb_mode = 0;
......@@ -1930,6 +2211,12 @@ int main(int argc, char **argv) {
latencyMode();
}
/* Latency distribution mode */
if (config.latency_dist_mode) {
if (cliConnect(0) == REDIS_ERR) exit(1);
latencyDistMode();
}
/* Slave mode */
if (config.slave_mode) {
if (cliConnect(0) == REDIS_ERR) exit(1);
......@@ -1967,6 +2254,12 @@ int main(int argc, char **argv) {
scanMode();
}
/* LRU test mode */
if (config.lru_test_mode) {
if (cliConnect(0) == REDIS_ERR) exit(1);
LRUTestMode();
}
/* Intrinsic latency mode */
if (config.intrinsic_latency_mode) intrinsicLatencyMode();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册