Unverified Commit 6801a3ce authored by Yves LeBras's avatar Yves LeBras Committed by GitHub
Browse files

redis-cli --keystats and --keystats-samples with --top and --cursor (#12826)



Added `--keystats` and `--keystats-samples <n>` commands.

The new commands combines memkeys and bigkeys with additional
distribution data.
We often run memkeys and bigkeys one after the other. It will be
convenient to just have one command.
Distribution and top 10 key sizes are useful when we have multiple keys
taking a lot of memory.

Like for memkeys and bigkeys, we can use `-i <n>` and Ctrl-C to
interrupt the scan loop. It will still show the statistics on the keys
sampled so far.
We can use two new optional parameters:
- `--cursor <n>` to continue from the last cursor after an interruption
of the scan.
- `--top <n>` to change the number of top key sizes (10 by default).

Implemented a fix for the `--memkeys-samples 0` issue in order to use
`--keystats-samples 0`.

Used hdr_histogram for the key size distribution.

For key length, hdr_histogram seemed overkill and preset ranges were
used.

The memory used by keystats with hdr_histogram is around 7MB (compared
to 3MB for memkeys or bigkeys).

Execution time is somewhat equivalent to adding memkeys and bigkeys
time. Each scan loop is having more commands (key type, key size, key
length/cardinality).

We can redirect the output to a file. In that case, no color or text
refresh will happen.

Limitation:
- As the information printed during the loop is refreshed (moving cursor
up), stderr and information not fitting in the terminal window (width or
height) might create some refresh issues.

Comments:
- config.top_sizes_limit could be used globally like config.count, but
it is passed as parameter to be consistent with config.memkeys_samples.
- Not sure if we should move some utility functions to cli-common.c.

Got some tips and help from @ofirluzon.

---------
Co-authored-by: default avatarBinbin <binloveplay1314@qq.com>
Co-authored-by: default avatarViktor Söderqvist <viktor.soderqvist@est.tech>
Co-authored-by: default avatardebing.sun <debing.sun@redis.com>
parent e92363e2
......@@ -429,7 +429,7 @@ $(TLS_MODULE_NAME): $(REDIS_SERVER_NAME)
# redis-cli
$(REDIS_CLI_NAME): $(REDIS_CLI_OBJ)
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o $(FINAL_LIBS) $(TLS_CLIENT_LIBS)
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o ../deps/hdr_histogram/libhdrhistogram.a $(FINAL_LIBS) $(TLS_CLIENT_LIBS)
# redis-benchmark
$(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ)
......
......@@ -9,6 +9,7 @@
#include "fmacros.h"
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
......@@ -43,6 +44,7 @@
#include "cli_common.h"
#include "mt19937-64.h"
#include "cli_commands.h"
#include "hdr_histogram.h"
#define UNUSED(V) ((void) V)
......@@ -140,6 +142,10 @@
/* DNS lookup */
#define NET_IP_STR_LEN 46 /* INET6_ADDRSTRLEN is 46 */
#define REFRESH_INTERVAL 300 /* milliseconds */
#define IS_TTY_OR_FAKETTY() (isatty(STDOUT_FILENO) || getenv("FAKETTY"))
/* --latency-dist palettes. */
int spectrum_palette_color_size = 19;
int spectrum_palette_color[] = {0,233,234,235,237,239,241,243,245,247,144,143,142,184,226,214,208,202,196};
......@@ -224,8 +230,11 @@ static struct config {
char *rdb_filename;
int bigkeys;
int memkeys;
unsigned memkeys_samples;
long long memkeys_samples;
int hotkeys;
int keystats;
unsigned long long cursor;
unsigned long top_sizes_limit;
int stdin_lastarg; /* get last arg from stdin. (-x option) */
int stdin_tag_arg; /* get <tag> arg from stdin. (-X option) */
char *stdin_tag_name; /* Placeholder(tag name) for user input. */
......@@ -382,6 +391,37 @@ void dictListDestructor(dict *d, void *val)
listRelease((list*)val);
}
/* Erase the lines before printing, and returns the number of lines printed */
int cleanPrintfln(char *fmt, ...) {
va_list args;
char buf[1024]; /* limitation */
int char_count, line_count = 0;
/* Clear the line if in TTY */
if (IS_TTY_OR_FAKETTY()) {
printf("\033[2K\r");
}
va_start(args, fmt);
char_count = vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
if (char_count >= (int)sizeof(buf)) {
fprintf(stderr, "Warning: String was trimmed in cleanPrintln\n");
}
char *position, *string = buf;
while ((position = strchr(string, '\n')) != NULL) {
int line_length = (int)(position - string);
printf("%.*s\n", line_length, string);
string = position + 1;
line_count++;
}
printf("%s\n", string);
return line_count + 1;
}
/*------------------------------------------------------------------------------
* Help functions
*--------------------------------------------------------------------------- */
......@@ -2739,12 +2779,63 @@ static int parseOptions(int argc, char **argv) {
config.bigkeys = 1;
} else if (!strcmp(argv[i],"--memkeys")) {
config.memkeys = 1;
config.memkeys_samples = 0; /* use redis default */
config.memkeys_samples = -1; /* use redis default */
} else if (!strcmp(argv[i],"--memkeys-samples") && !lastarg) {
char *endptr;
config.memkeys = 1;
config.memkeys_samples = atoi(argv[++i]);
config.keystats = 1;
config.memkeys_samples = strtoll(argv[++i], &endptr, 10);
if (*endptr) {
fprintf(stderr, "--memkeys-samples conversion error.\n");
exit(1);
}
if (config.memkeys_samples < 0) {
fprintf(stderr, "--memkeys-samples value should be positive.\n");
exit(1);
}
} else if (!strcmp(argv[i],"--hotkeys")) {
config.hotkeys = 1;
} else if (!strcmp(argv[i], "--keystats")) {
config.keystats = 1;
config.memkeys_samples = -1; /* use redis default */
} else if (!strcmp(argv[i],"--keystats-samples") && !lastarg) {
char *endptr;
config.keystats = 1;
config.memkeys_samples = strtoll(argv[++i], &endptr, 10);
if (*endptr) {
fprintf(stderr, "--keystats-samples conversion error.\n");
exit(1);
}
if (config.memkeys_samples < 0) {
fprintf(stderr, "--keystats-samples value should be positive.\n");
exit(1);
}
} else if (!strcmp(argv[i],"--cursor") && !lastarg) {
i++;
char sign = *argv[i];
char *endptr;
config.cursor = strtoull(argv[i], &endptr, 10);
if (*endptr) {
fprintf(stderr, "--cursor conversion error.\n");
exit(1);
}
if (sign == '-' && config.cursor != 0) {
fprintf(stderr, "--cursor should be followed by a positive integer.\n");
exit(1);
}
} else if (!strcmp(argv[i],"--top") && !lastarg) {
i++;
char sign = *argv[i];
char *endptr;
config.top_sizes_limit = strtoull(argv[i], &endptr, 10);
if (*endptr) {
fprintf(stderr, "--top conversion error.\n");
exit(1);
}
if (sign == '-' && config.top_sizes_limit != 0) {
fprintf(stderr, "--top should be followed by a positive integer.\n");
exit(1);
}
} else if (!strcmp(argv[i],"--eval") && !lastarg) {
config.eval = argv[++i];
} else if (!strcmp(argv[i],"--ldb")) {
......@@ -3080,6 +3171,13 @@ version,tls_usage);
" --memkeys Sample Redis keys looking for keys consuming a lot of memory.\n"
" --memkeys-samples <n> Sample Redis keys looking for keys consuming a lot of memory.\n"
" And define number of key elements to sample\n"
" --keystats Sample Redis keys looking for keys memory size and length (combine bigkeys and memkeys).\n"
" --keystats-samples <n> Sample Redis keys looking for keys memory size and length.\n"
" And define number of key elements to sample (only for memory usage).\n"
" --cursor <n> Start the scan at the cursor <n> (usually after a Ctrl-C).\n"
" Optionally used with --keystats and --keystats-samples.\n"
" --top <n> To display <n> top key sizes (default: 10).\n"
" Optionally used with --keystats and --keystats-samples.\n"
" --hotkeys Sample Redis keys looking for hot keys.\n"
" only works when maxmemory-policy is *lfu.\n"
" --scan List all keys using the SCAN command.\n"
......@@ -9029,7 +9127,7 @@ static void getKeyTypes(dict *types_dict, redisReply *keys, typeinfo **types) {
static void getKeySizes(redisReply *keys, typeinfo **types,
unsigned long long *sizes, int memkeys,
unsigned memkeys_samples)
long long memkeys_samples)
{
redisReply *reply;
unsigned int i;
......@@ -9044,7 +9142,7 @@ static void getKeySizes(redisReply *keys, typeinfo **types,
const char* argv[] = {types[i]->sizecmd, keys->element[i]->str};
size_t lens[] = {strlen(types[i]->sizecmd), keys->element[i]->len};
redisAppendCommandArgv(context, 2, argv, lens);
} else if (memkeys_samples==0) {
} else if (memkeys_samples == -1) {
const char* argv[] = {"MEMORY", "USAGE", keys->element[i]->str};
size_t lens[] = {6, 5, keys->element[i]->len};
redisAppendCommandArgv(context, 3, argv, lens);
......@@ -9108,7 +9206,10 @@ static void sendReadOnly(void) {
freeReplyObject(read_reply);
}
static void findBigKeys(int memkeys, unsigned memkeys_samples) {
static int displayKeyStatsProgressbar(unsigned long long sampled,
unsigned long long total_keys);
static void findBigKeys(int memkeys, long long memkeys_samples) {
unsigned long long sampled = 0, total_keys, totlen=0, *sizes=NULL, it=0, scan_loops = 0;
redisReply *reply, *keys;
unsigned int arrsize=0, i;
......@@ -9116,6 +9217,7 @@ static void findBigKeys(int memkeys, unsigned memkeys_samples) {
dictEntry *de;
typeinfo **types = NULL;
double pct;
long long refresh_time = mstime();
dict *types_dict = dictCreate(&typeinfoDictType);
typeinfo_add(types_dict, "string", &type_string);
......@@ -9186,19 +9288,44 @@ static void findBigKeys(int memkeys, unsigned memkeys_samples) {
exit(1);
}
printf(
"[%05.2f%%] Biggest %-6s found so far '%s' with %llu %s\n",
pct, type->name, type->biggest_key, sizes[i],
!memkeys? type->sizeunit: "bytes");
/* We only show the original progress output when writing to a file */
if (!IS_TTY_OR_FAKETTY()) {
printf("[%05.2f%%] Biggest %-6s found so far %s with %llu %s\n",
pct, type->name, type->biggest_key, sizes[i],
!memkeys? type->sizeunit: "bytes");
}
/* Keep track of the biggest size for this type */
type->biggest = sizes[i];
}
/* Update overall progress */
if(sampled % 1000000 == 0) {
/* Update overall progress
* We only show the original progress output when writing to a file */
if (sampled % 1000000 == 0 && !IS_TTY_OR_FAKETTY()) {
printf("[%05.2f%%] Sampled %llu keys so far\n", pct, sampled);
}
/* Show the progress bar in TTY */
if (mstime() > refresh_time + REFRESH_INTERVAL && IS_TTY_OR_FAKETTY()) {
int line_count = 0;
refresh_time = mstime();
line_count = displayKeyStatsProgressbar(sampled, total_keys);
line_count += cleanPrintfln("");
di = dictGetIterator(types_dict);
while ((de = dictNext(di))) {
typeinfo *current_type = dictGetVal(de);
if (current_type->biggest > 0) {
line_count += cleanPrintfln("Biggest %-9s found so far %s with %llu %s",
current_type->name, current_type->biggest_key, current_type->biggest,
!memkeys? current_type->sizeunit: "bytes");
}
}
dictReleaseIterator(di);
printf("\033[%dA\r", line_count);
}
}
/* Sleep if we've been directed to do so */
......@@ -9209,13 +9336,31 @@ static void findBigKeys(int memkeys, unsigned memkeys_samples) {
freeReplyObject(reply);
} while(force_cancel_loop == 0 && it != 0);
/* Final progress bar if TTY */
if (IS_TTY_OR_FAKETTY()) {
displayKeyStatsProgressbar(sampled, total_keys);
/* Clean the types info shown during the progress bar */
int line_count = 0;
di = dictGetIterator(types_dict);
while ((de = dictNext(di)))
line_count += cleanPrintfln("");
dictReleaseIterator(di);
printf("\033[%dA\r", line_count);
}
if(types) zfree(types);
if(sizes) zfree(sizes);
/* We're done */
printf("\n-------- summary -------\n\n");
if (force_cancel_loop) printf("[%05.2f%%] ", pct);
printf("Sampled %llu keys in the keyspace!\n", sampled);
/* Show percentage and sampled output when writing to a file */
if (!IS_TTY_OR_FAKETTY()) {
if (force_cancel_loop) printf("[%05.2f%%] ", pct);
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);
......@@ -9224,7 +9369,7 @@ static void findBigKeys(int memkeys, unsigned memkeys_samples) {
while ((de = dictNext(di))) {
typeinfo *type = dictGetVal(de);
if(type->biggest_key) {
printf("Biggest %6s found '%s' has %llu %s\n", type->name, type->biggest_key,
printf("Biggest %6s found %s has %llu %s\n", type->name, type->biggest_key,
type->biggest, !memkeys? type->sizeunit: "bytes");
}
}
......@@ -9290,8 +9435,10 @@ static void findHotKeys(void) {
unsigned long long counters[HOTKEYS_SAMPLE] = {0};
sds hotkeys[HOTKEYS_SAMPLE] = {NULL};
unsigned long long sampled = 0, total_keys, *freqs = NULL, it = 0, scan_loops = 0;
unsigned int arrsize = 0, i, k;
unsigned int arrsize = 0, i;
int k;
double pct;
long long refresh_time = mstime();
signal(SIGINT, longStatLoopModeStop);
/* Total keys pre scanning */
......@@ -9332,8 +9479,10 @@ static void findHotKeys(void) {
/* Now update our stats */
for(i=0;i<keys->elements;i++) {
sampled++;
/* Update overall progress */
if(sampled % 1000000 == 0) {
/* Update overall progress.
* Only show the original progress output when writing to a file */
if (sampled % 1000000 == 0 && !IS_TTY_OR_FAKETTY()) {
printf("[%05.2f%%] Sampled %llu keys so far\n", pct, sampled);
}
......@@ -9351,9 +9500,30 @@ static void findHotKeys(void) {
}
counters[k] = freqs[i];
hotkeys[k] = sdscatrepr(sdsempty(), keys->element[i]->str, keys->element[i]->len);
printf(
"[%05.2f%%] Hot key '%s' found so far with counter %llu\n",
pct, hotkeys[k], freqs[i]);
/* Only show the original progress output when writing to a file */
if (!IS_TTY_OR_FAKETTY()) {
printf("[%05.2f%%] Hot key %s found so far with counter %llu\n",
pct, hotkeys[k], freqs[i]);
}
}
/* Show the progress bar in TTY */
if (mstime() > refresh_time + REFRESH_INTERVAL && IS_TTY_OR_FAKETTY()) {
int line_count = 0;
refresh_time = mstime();
line_count = displayKeyStatsProgressbar(sampled, total_keys);
line_count += cleanPrintfln("");
for (k = HOTKEYS_SAMPLE - 1; k >= 0; k--) {
if (counters[k] > 0) {
line_count += cleanPrintfln("hot key found with counter: %llu\tkeyname: %s",
counters[k], hotkeys[k]);
}
}
printf("\033[%dA\r", line_count);
}
/* Sleep if we've been directed to do so */
......@@ -9364,16 +9534,30 @@ static void findHotKeys(void) {
freeReplyObject(reply);
} while(force_cancel_loop ==0 && it != 0);
/* Final progress bar in TTY */
if (IS_TTY_OR_FAKETTY()) {
displayKeyStatsProgressbar(sampled, total_keys);
/* clean the types info shown during the progress bar */
int line_count = 0;
for (k = 0; k <= HOTKEYS_SAMPLE; k++)
line_count += cleanPrintfln("");
printf("\033[%dA\r", line_count);
}
if (freqs) zfree(freqs);
/* We're done */
printf("\n-------- summary -------\n\n");
if(force_cancel_loop)printf("[%05.2f%%] ",pct);
printf("Sampled %llu keys in the keyspace!\n", sampled);
for (i=1; i<= HOTKEYS_SAMPLE; i++) {
k = HOTKEYS_SAMPLE - i;
if(counters[k]>0) {
/* Show the original output when writing to a file */
if (!IS_TTY_OR_FAKETTY()) {
if(force_cancel_loop) printf("[%05.2f%%] ",pct);
printf("Sampled %llu keys in the keyspace!\n", sampled);
}
for (k = HOTKEYS_SAMPLE - 1; k >= 0; k--) {
if (counters[k] > 0) {
printf("hot key found with counter: %llu\tkeyname: %s\n", counters[k], hotkeys[k]);
sdsfree(hotkeys[k]);
}
......@@ -9418,9 +9602,11 @@ static long getLongInfoField(char *info, char *field) {
}
/* Convert number of bytes into a human readable string of the form:
* 100B, 2G, 100M, 4K, and so forth. */
void bytesToHuman(char *s, size_t size, long long n) {
* 1003B, 4.03K, 100.00M, 2.32G, 3.01T
* Returns the parameter `s` containing the converted number. */
char *bytesToHuman(char *s, size_t size, long long n) {
double d;
char *r = s;
if (n < 0) {
*s = '-';
......@@ -9430,7 +9616,6 @@ void bytesToHuman(char *s, size_t size, long long n) {
if (n < 1024) {
/* Bytes */
snprintf(s,size,"%lldB",n);
return;
} else if (n < (1024*1024)) {
d = (double)n/(1024);
snprintf(s,size,"%.2fK",d);
......@@ -9440,7 +9625,12 @@ void bytesToHuman(char *s, size_t size, long long n) {
} else if (n < (1024LL*1024*1024*1024)) {
d = (double)n/(1024LL*1024*1024);
snprintf(s,size,"%.2fG",d);
} else if (n < (1024LL*1024*1024*1024*1024)) {
d = (double)n/(1024LL*1024*1024*1024);
snprintf(s,size,"%.2fT",d);
}
return r;
}
static void statMode(void) {
......@@ -9822,6 +10012,559 @@ void testHintSuite(char *filename) {
exit(fail);
}
/*------------------------------------------------------------------------------
* Keystats
*--------------------------------------------------------------------------- */
/* Key name length distribution. */
typedef struct size_dist_entry {
unsigned long long size; /* Key name size in bytes. */
unsigned long long count; /* Number of key names that are less or equal to the size. */
} size_dist_entry;
typedef struct size_dist {
unsigned long long total_count; /* Total number of key names in the distribution. */
unsigned long long total_size; /* Sum of all the key name sizes in bytes. */
unsigned long long max_size; /* Highest key name size in bytes. */
size_dist_entry *size_dist; /* Array of sizes and key names count per size. */
} size_dist;
/* distribution is an array initialized with last element {0, 0}
* for instance: size_dist_entry distribution[] = { {32, 0}, {256, 0}, {0, 0} }; */
static void sizeDistInit(size_dist *dist, size_dist_entry *distribution) {
dist->max_size = 0;
dist->total_count = 0;
dist->total_size = 0;
dist->size_dist = distribution;
}
static void addSizeDist(size_dist *dist, unsigned long long size) {
dist->total_count++;
dist->total_size += size;
if (size > dist->max_size)
dist->max_size = size;
int j;
for (j=0; dist->size_dist[j].size && size > dist->size_dist[j].size; j++);
dist->size_dist[j].count++;
}
static int displayKeyStatsLengthDist(size_dist *dist) {
int line_count = 0;
unsigned long long total_keys = 0, size;
char buf[2][256];
line_count += cleanPrintfln("Key name length Percentile Total keys");
line_count += cleanPrintfln("--------------- ---------- -----------");
for (int i=0; dist->size_dist[i].size; i++) {
if (dist->size_dist[i].count) {
if (dist->max_size < dist->size_dist[i].size) {
size = dist->max_size;
} else {
size = dist->size_dist[i].size;
}
total_keys += dist->size_dist[i].count;
line_count += cleanPrintfln("%15s %9.4f%% %11llu",
bytesToHuman(buf[1], sizeof(buf[1]), size),
(double)100 * total_keys / dist->total_count,
total_keys);
}
}
if (total_keys < dist->total_count) {
line_count += cleanPrintfln(" inf %9.4f%% %11llu", 100.0, dist->total_count);
}
line_count += cleanPrintfln("Total key length is %s (%s avg)",
bytesToHuman(buf[0], sizeof(buf[0]), dist->total_size),
dist->total_count ? bytesToHuman(buf[1], sizeof(buf[1]), dist->total_size/dist->total_count) : "0");
return line_count;
}
#define PROGRESSBAR_WIDTH 60
static int displayKeyStatsProgressbar(unsigned long long sampled,
unsigned long long total_keys)
{
int line_count = 0;
char progressbar[512];
char buf[2][128];
/* We can go over 100% if keys are added in the middle of the scans.
* Cap at 100% or the progressbar memset will overflow. */
double completion_pct = total_keys ? sampled < total_keys ? (double) sampled/total_keys : 1 : 0;
/* If we are not redirecting to a file, build the progress bar */
if (IS_TTY_OR_FAKETTY()) {
int completed_width = (int)round(PROGRESSBAR_WIDTH * completion_pct);
memset(buf[0], '|', completed_width);
buf[0][completed_width]= '\0';
int uncompleted_width = PROGRESSBAR_WIDTH - completed_width;
memset(buf[1], '-', uncompleted_width);
buf[1][uncompleted_width]= '\0';
char red[] = "\033[31m";
char green[] = "\033[32m";
char default_color[] = "\033[39m";
snprintf(progressbar, sizeof(progressbar), "%s%s%s%s%s",
green, buf[0], red, buf[1], default_color);
} else {
snprintf(progressbar, sizeof(progressbar), "%s", "keys scanned");
}
line_count += cleanPrintfln("%6.2f%% %s", completion_pct * 100, progressbar);
line_count += cleanPrintfln("Keys sampled: %llu", sampled);
return line_count;
}
static int displayKeyStatsSizeType(dict *memkeys_types_dict) {
dictIterator *di;
dictEntry *de;
int line_count = 0;
char buf[256];
line_count += cleanPrintfln("--- Top size per type ---");
di = dictGetIterator(memkeys_types_dict);
while ((de = dictNext(di))) {
typeinfo *type = dictGetVal(de);
if (type->biggest_key) {
line_count += cleanPrintfln("%-10s %s is %s",
type->name, type->biggest_key,
bytesToHuman(buf, sizeof(buf),type->biggest));
}
}
dictReleaseIterator(di);
return line_count;
}
static int displayKeyStatsLengthType(dict *bigkeys_types_dict) {
dictIterator *di;
dictEntry *de;
int line_count = 0;
char buf[256];
line_count += cleanPrintfln("--- Top length and cardinality per type ---");
di = dictGetIterator(bigkeys_types_dict);
while ((de = dictNext(di))) {
typeinfo *type = dictGetVal(de);
if (type->biggest_key) {
if (!strcmp(type->sizeunit, "bytes")) {
bytesToHuman(buf, sizeof(buf), type->biggest);
} else {
snprintf(buf, sizeof(buf), "%llu %s", type->biggest, type->sizeunit);
}
line_count += cleanPrintfln("%-10s %s has %s", type->name, type->biggest_key, buf);
}
}
dictReleaseIterator(di);
return line_count;
}
static int displayKeyStatsSizeDist(struct hdr_histogram *keysize_histogram) {
int line_count = 0;
double percentile;
char size[32], mean[32], stddev[32];
struct hdr_iter iter;
int64_t last_displayed_cumulative_count = 0;
hdr_iter_percentile_init(&iter, keysize_histogram, 1);
line_count += cleanPrintfln("Key size Percentile Total keys");
line_count += cleanPrintfln("-------- ---------- -----------");
while (hdr_iter_next(&iter)) {
/* Skip repeat in hdr_histogram cumulative_count, and set the last line
* to 100% when total_count is reached. For instance:
* 140.68K 99.9969% 50013
* 140.68K 99.9977% 50013
* 2.04G 99.9985% 50014
* 2.04G 100.0000% 50014
* Will display:
* 140.68K 99.9969% 50013
* 2.04G 100.0000% 50014 */
if (iter.cumulative_count != last_displayed_cumulative_count) {
if (iter.cumulative_count == iter.h->total_count) {
percentile = 100;
} else {
percentile = iter.specifics.percentiles.percentile;
}
line_count += cleanPrintfln("%8s %9.4f%% %11lld",
bytesToHuman(size, sizeof(size), iter.highest_equivalent_value),
percentile,
iter.cumulative_count);
last_displayed_cumulative_count = iter.cumulative_count;
}
}
bytesToHuman(mean, sizeof(mean),hdr_mean(keysize_histogram));
bytesToHuman(stddev, sizeof(stddev),hdr_stddev(keysize_histogram));
line_count += cleanPrintfln("Note: 0.01%% size precision, Mean: %s, StdDeviation: %s", mean, stddev);
return line_count;
}
static int displayKeyStatsType(unsigned long long sampled,
dict *memkeys_types_dict,
dict *bigkeys_types_dict)
{
dictIterator *di;
dictEntry *de;
int line_count = 0;
char total_size[64], size_avg[64], total_length[64], length_avg[64];
line_count += cleanPrintfln("Type Total keys Keys %% Tot size Avg size Total length/card Avg ln/card");
line_count += cleanPrintfln("--------- ------------ ------- -------- -------- ------------------ -----------");
di = dictGetIterator(memkeys_types_dict);
while ((de = dictNext(di))) {
typeinfo *memkey_type = dictGetVal(de);
if (memkey_type->count) {
/* Key count, percentage, memkeys info */
bytesToHuman(total_size, sizeof(total_size), memkey_type->totalsize);
bytesToHuman(size_avg, sizeof(size_avg), memkey_type->totalsize/memkey_type->count);
strncpy(total_length, " - ", sizeof(total_length));
strncpy(length_avg, " - ", sizeof(length_avg));
/* bigkeys info */
dictEntry *bk_de = dictFind(bigkeys_types_dict, memkey_type->name);
if (bk_de) { /* If we have it in memkeys it should be in bigkeys */
typeinfo *bigkey_type = dictGetVal(bk_de);
if (bigkey_type->sizecmd && bigkey_type->count) {
double avg = (double)bigkey_type->totalsize/bigkey_type->count;
if (!strcmp(bigkey_type->sizeunit, "bytes")) {
bytesToHuman(total_length, sizeof(total_length), bigkey_type->totalsize);
bytesToHuman(length_avg, sizeof(length_avg), (long long)round(avg)); /* better than truncating */
} else {
snprintf(total_length, sizeof(total_length), "%llu %s", bigkey_type->totalsize, bigkey_type->sizeunit);
snprintf(length_avg, sizeof(length_avg), "%.2f", avg);
}
}
}
/* Print the line for the given Redis type */
line_count += cleanPrintfln("%-10s %11llu %6.2f%% %8s %8s %18s %11s",
memkey_type->name, memkey_type->count,
sampled ? 100 * (double)memkey_type->count/sampled : 0,
total_size, size_avg, total_length, length_avg);
}
}
dictReleaseIterator(di);
return line_count;
}
typedef struct key_info {
unsigned long long size;
char type_name[10]; /* Key type name seems to be 9 char max + \0 */
sds key_name;
} key_info;
static int displayKeyStatsTopSizes(list *top_key_sizes, unsigned long top_sizes_limit) {
int line_count = 0, i = 0;
line_count += cleanPrintfln("--- Top %llu key sizes ---", top_sizes_limit);
char buffer[32];
listIter *iter = listGetIterator(top_key_sizes, AL_START_HEAD);
listNode *node;
while ((node = listNext(iter)) != NULL) {
key_info *key = (key_info*) listNodeValue(node);
line_count += cleanPrintfln("%3d %8s %-10s %s", ++i, bytesToHuman(buffer, sizeof(buffer), key->size),
key->type_name, key->key_name);
}
listReleaseIterator(iter);
return line_count;
}
static key_info *createKeySizeInfo(char *key_name, size_t key_name_len, char *key_type, unsigned long long size) {
key_info *key = zmalloc(sizeof(key_info));
key->size = size;
snprintf(key->type_name, sizeof(key->type_name), "%s", key_type);
key->key_name = sdscatrepr(sdsempty(), key_name, key_name_len);
if (!key->key_name) {
fprintf(stderr, "Failed to allocate memory for key name.\n");
exit(1);
}
return key;
}
/* Insert key info in topkeys sorted by size (from high to low size).
* Keep a maximum of config.top_sizes_limit items in topkeys list.
* key_name and type_name are copied.
* Return: 0 size was not added (too small), 1 size was inserted. */
static int updateTopSizes(char *key_name, size_t key_name_len, unsigned long long key_size,
char *type_name, list *topkeys, unsigned long top_sizes_limit)
{
listNode *node;
listIter *iter;
key_info *new_node;
/* Check if we do not need to add to the list */
if (top_sizes_limit != 0 &&
topkeys->len == top_sizes_limit &&
key_size <= ((key_info*)topkeys->tail->value)->size){
return 0;
}
/* Find where to insert the new key size */
iter = listGetIterator(topkeys, AL_START_HEAD);
do {
node = listNext(iter);
} while (node != NULL && key_size <= ((key_info*)node->value)->size);
listReleaseIterator(iter);
new_node = createKeySizeInfo(key_name, key_name_len, type_name, key_size);
if (node) {
/* Insert before the node */
listInsertNode(topkeys, node, new_node, 0);
} else {
/* Insert as the last node */
listAddNodeTail(topkeys, new_node);
}
/* Trim to stay within the limit */
if (topkeys->len == top_sizes_limit + 1) {
sdsfree(((key_info*)topkeys->tail->value)->key_name);
listDelNode(topkeys, topkeys->tail); /* list->free is set */
}
return 1;
}
static void displayKeyStats(unsigned long long sampled, unsigned long long total_keys,
unsigned long long total_size, dict *memkeys_types_dict,
dict *bigkeys_types_dict, list *top_key_sizes,
unsigned long top_sizes_limit, int move_cursor_up)
{
int line_count = 0;
char buf[256];
line_count += displayKeyStatsProgressbar(sampled, total_keys);
line_count += cleanPrintfln("Keys size: %s", bytesToHuman(buf, sizeof(buf), total_size));
line_count += cleanPrintfln("");
line_count += displayKeyStatsTopSizes(top_key_sizes, top_sizes_limit);
line_count += cleanPrintfln("");
line_count += displayKeyStatsSizeType(memkeys_types_dict);
line_count += cleanPrintfln("");
line_count += displayKeyStatsLengthType(bigkeys_types_dict);
/* If we need to refresh the stats */
if (move_cursor_up) {
printf("\033[%dA\r", line_count);
}
fflush(stdout);
}
static void updateKeyType(redisReply *element, unsigned long long size, typeinfo *type) {
type->totalsize += size;
type->count++;
if (type->biggest<size) {
/* Keep track of biggest key name for this type */
if (type->biggest_key)
sdsfree(type->biggest_key);
type->biggest_key = sdsnewlen(element->str, element->len);
if (!type->biggest_key) {
fprintf(stderr, "Failed to allocate memory for key!\n");
exit(1);
}
/* Keep track of the biggest size for this type */
type->biggest = size;
}
}
static void keyStats(long long memkeys_samples, unsigned long long cursor, unsigned long top_sizes_limit) {
unsigned long long sampled = 0, total_keys, total_size = 0, it = 0, scan_loops = 0;
unsigned long long *memkeys_sizes = NULL, *bigkeys_sizes = NULL;
redisReply *reply, *keys;
unsigned int array_size = 0, i;
typeinfo **memkeys_types = NULL, **bigkeys_types = NULL;
list *top_sizes;
long long refresh_time = mstime();
if (cursor != 0) {
it = cursor;
}
if ((top_sizes = listCreate()) == NULL) {
fprintf(stderr, "top_sizes list creation failed.\n");
exit(1);
}
top_sizes->free = zfree;
dict *memkeys_types_dict = dictCreate(&typeinfoDictType);
typeinfo_add(memkeys_types_dict, "string", &type_string);
typeinfo_add(memkeys_types_dict, "list", &type_list);
typeinfo_add(memkeys_types_dict, "set", &type_set);
typeinfo_add(memkeys_types_dict, "hash", &type_hash);
typeinfo_add(memkeys_types_dict, "zset", &type_zset);
typeinfo_add(memkeys_types_dict, "stream", &type_stream);
/* We could use only one typeinfo dictionary if we add new fields to save
* both memkey and bigkey info. Not sure it would make sense in findBigKeys(). */
dict *bigkeys_types_dict = dictCreate(&typeinfoDictType);
typeinfo_add(bigkeys_types_dict, "string", &type_string);
typeinfo_add(bigkeys_types_dict, "list", &type_list);
typeinfo_add(bigkeys_types_dict, "set", &type_set);
typeinfo_add(bigkeys_types_dict, "hash", &type_hash);
typeinfo_add(bigkeys_types_dict, "zset", &type_zset);
typeinfo_add(bigkeys_types_dict, "stream", &type_stream);
size_dist key_length_dist;
size_dist_entry distribution[] = {
{1<<5, 0}, /* 32 B (sds) */
{1<<8, 0}, /* 256 B (sds) */
{1<<16, 0}, /* 64 KB (sds and Redis Enterprise key name max length) */
{1024*1024, 0}, /* 1 MB */
{16*1024*1024, 0}, /* 16 MB */
{128*1024*1024, 0}, /* 128 MB */
{512*1024*1024, 0}, /* 512 MB (max String size) */
{0, 0}, /* Sizes above the last entry */
};
sizeDistInit(&key_length_dist, distribution);
struct hdr_histogram *keysize_histogram;
/* Record max of 1TB for a key size should cover all keys.
* significant_figures == 4 (0.01% precision on key size) */
if (hdr_init(1, 1ULL*1024*1024*1024*1024, 4, &keysize_histogram)) {
fprintf(stderr, "Keystats hdr init error\n");
exit(1);
}
signal(SIGINT, longStatLoopModeStop);
/* Total keys pre scanning */
total_keys = getDbSize();
/* Status message */
printf("\n# Scanning the entire keyspace to find the biggest keys and distribution information.\n");
printf("# Use -i 0.1 to sleep 0.1 sec per 100 SCAN commands (not usually needed).\n");
printf("# Use --cursor <n> to start the scan at the cursor <n> (usually after a Ctrl-C).\n");
printf("# Use --top <n> to display <n> top key sizes (default is 10).\n");
printf("# Ctrl-C to stop the scan.\n\n");
/* Use readonly in cluster */
sendReadOnly();
/* SCAN loop */
do {
/* Grab some keys and point to the keys array */
reply = sendScan(&it);
scan_loops++;
keys = reply->element[1];
/* Reallocate our type and size array if we need to */
if (keys->elements > array_size) {
memkeys_types = zrealloc(memkeys_types, sizeof(typeinfo*)*keys->elements);
memkeys_sizes = zrealloc(memkeys_sizes, sizeof(unsigned long long)*keys->elements);
bigkeys_types = zrealloc(bigkeys_types, sizeof(typeinfo*)*keys->elements);
bigkeys_sizes = zrealloc(bigkeys_sizes, sizeof(unsigned long long)*keys->elements);
if (!memkeys_types || !memkeys_sizes || !bigkeys_types || !bigkeys_sizes) {
fprintf(stderr, "Failed to allocate storage for keys!\n");
exit(1);
}
array_size = keys->elements;
}
/* Retrieve types and sizes for memkeys */
getKeyTypes(memkeys_types_dict, keys, memkeys_types);
getKeySizes(keys, memkeys_types, memkeys_sizes, 1, memkeys_samples);
/* Retrieve types and sizes for bigkeys */
getKeyTypes(bigkeys_types_dict, keys, bigkeys_types);
getKeySizes(keys, bigkeys_types, bigkeys_sizes, 0, memkeys_samples);
for (i=0; i<keys->elements; i++) {
/* Skip keys that disappeared between SCAN and TYPE */
if (!memkeys_types[i] || !bigkeys_types[i]) {
continue;
}
total_size += memkeys_sizes[i];
sampled++;
updateTopSizes(keys->element[i]->str, keys->element[i]->len, memkeys_sizes[i],
memkeys_types[i]->name, top_sizes, top_sizes_limit);
updateKeyType(keys->element[i], memkeys_sizes[i], memkeys_types[i]);
updateKeyType(keys->element[i], bigkeys_sizes[i], bigkeys_types[i]);
/* Key Size distribution */
if (hdr_record_value(keysize_histogram, memkeys_sizes[i]) == 0) {
fprintf(stderr, "Value %llu not added in the hdr histogram.\n", memkeys_sizes[i]);
}
/* Key length distribution */
addSizeDist(&key_length_dist, keys->element[i]->len);
}
/* Refresh keystats info on regular basis */
if (mstime() > refresh_time + REFRESH_INTERVAL && IS_TTY_OR_FAKETTY()) {
displayKeyStats(sampled, total_keys, total_size, memkeys_types_dict, bigkeys_types_dict,
top_sizes, top_sizes_limit, 1);
refresh_time = mstime();
}
/* Sleep if we've been directed to do so */
if (config.interval && (scan_loops % 100) == 0) {
usleep(config.interval);
}
freeReplyObject(reply);
} while(force_cancel_loop == 0 && it != 0);
displayKeyStats(sampled, total_keys, total_size, memkeys_types_dict, bigkeys_types_dict, top_sizes,
top_sizes_limit, 0);
/* Additional data at the end of the SCAN loop.
* Using cleanPrintfln in case we want to print during the SCAN loop. */
cleanPrintfln("");
displayKeyStatsSizeDist(keysize_histogram);
cleanPrintfln("");
displayKeyStatsLengthDist(&key_length_dist);
cleanPrintfln("");
displayKeyStatsType(sampled, memkeys_types_dict, bigkeys_types_dict);
if (it != 0) {
printf("\n");
printf("Scan interrupted:\n");
printf("Use 'redis-cli --keystats --cursor %llu' to restart from the last cursor.\n", it);
}
if (memkeys_types) zfree(memkeys_types);
if (bigkeys_types) zfree(bigkeys_types);
if (memkeys_sizes) zfree(memkeys_sizes);
if (bigkeys_sizes) zfree(bigkeys_sizes);
dictRelease(memkeys_types_dict);
dictRelease(bigkeys_types_dict);
hdr_close(keysize_histogram);
/* sdsfree before listRelease */
listIter *iter = listGetIterator(top_sizes, AL_START_HEAD);
listNode *node;
while ((node = listNext(iter)) != NULL) {
key_info *key = (key_info*) listNodeValue(node);
sdsfree(key->key_name);
}
listReleaseIterator(iter);
listRelease(top_sizes); /* list->free is set */
exit(0);
}
/*------------------------------------------------------------------------------
* Program main()
*--------------------------------------------------------------------------- */
......@@ -9865,6 +10608,9 @@ int main(int argc, char **argv) {
config.pipe_timeout = REDIS_CLI_DEFAULT_PIPE_TIMEOUT;
config.bigkeys = 0;
config.memkeys = 0;
config.keystats = 0;
config.cursor = 0;
config.top_sizes_limit = 10;
config.hotkeys = 0;
config.stdin_lastarg = 0;
config.stdin_tag_arg = 0;
......@@ -10003,6 +10749,12 @@ int main(int argc, char **argv) {
findBigKeys(1, config.memkeys_samples);
}
/* Find big and large keys */
if (config.keystats) {
if (cliConnect(0) == REDIS_ERR) exit(1);
keyStats(config.memkeys_samples, config.cursor, config.top_sizes_limit);
}
/* Find hot keys */
if (config.hotkeys) {
if (cliConnect(0) == REDIS_ERR) exit(1);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment