From 927c08aca53a1aa9ba31677c3fb4ec7585c3d23f Mon Sep 17 00:00:00 2001 From: christianEQ Date: Tue, 27 Apr 2021 20:56:37 +0000 Subject: [PATCH 01/41] added keydb-diagnostic-tool binary (copy of benchmark) Former-commit-id: a2c0bce4cc1403e01e70508b4297cfe5e76643cc --- .gitignore | 1 + src/Makefile | 12 +- src/keydb-diagnostic-tool.cpp | 1830 +++++++++++++++++++++++++++++++++ 3 files changed, 1840 insertions(+), 3 deletions(-) create mode 100644 src/keydb-diagnostic-tool.cpp diff --git a/.gitignore b/.gitignore index 21f903288..450530f79 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ redis-check-rdb keydb-check-rdb redis-check-dump keydb-check-dump +keydb-diagnostic-tool redis-cli redis-sentinel redis-server diff --git a/src/Makefile b/src/Makefile index 9e4070d4a..11516e15c 100644 --- a/src/Makefile +++ b/src/Makefile @@ -303,8 +303,9 @@ REDIS_BENCHMARK_NAME=keydb-benchmark$(PROG_SUFFIX) REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o siphash.o redis-benchmark.o storage-lite.o fastlock.o new.o $(ASM_OBJ) REDIS_CHECK_RDB_NAME=keydb-check-rdb$(PROG_SUFFIX) REDIS_CHECK_AOF_NAME=keydb-check-aof$(PROG_SUFFIX) +REDIS_DIAGNOSTIC_NAME=keydb-diagnostic-tool$(PROG_SUFFIX) -all: $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME) +all: $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME) $(REDIS_DIAGNOSTIC_NAME) @echo "" @echo "Hint: It's a good idea to run 'make test' ;)" @echo "" @@ -376,6 +377,10 @@ $(REDIS_CLI_NAME): $(REDIS_CLI_OBJ) $(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ) $(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a $(FINAL_LIBS) +# keydb-diagnostic-tool +$(REDIS_DIAGNOSTIC_NAME): $(REDIS_BENCHMARK_OBJ) + $(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a $(FINAL_LIBS) + dict-benchmark: dict.cpp zmalloc.cpp sds.c siphash.c $(REDIS_CC) $(FINAL_CFLAGS) $^ -D DICT_BENCHMARK_MAIN -o $@ $(FINAL_LIBS) @@ -395,7 +400,7 @@ DEP = $(REDIS_SERVER_OBJ:%.o=%.d) $(REDIS_CLI_OBJ:%.o=%.d) $(REDIS_BENCHMARK_OBJ $(KEYDB_AS) $< -o $@ clean: - rm -rf $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME) *.o *.gcda *.gcno *.gcov KeyDB.info lcov-html Makefile.dep dict-benchmark + rm -rf $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME) $(REDIS_DIAGNOSTIC_NAME) *.o *.gcda *.gcno *.gcov KeyDB.info lcov-html Makefile.dep dict-benchmark rm -f $(DEP) .PHONY: clean @@ -459,7 +464,8 @@ install: all $(REDIS_INSTALL) $(REDIS_CLI_NAME) $(INSTALL_BIN) $(REDIS_INSTALL) $(REDIS_CHECK_RDB_NAME) $(INSTALL_BIN) $(REDIS_INSTALL) $(REDIS_CHECK_AOF_NAME) $(INSTALL_BIN) + $(REDIS_INSTALL) $(REDIS_DIAGNOSTIC_NAME) $(INSTALL_BIN) @ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_SENTINEL_NAME) uninstall: - rm -f $(INSTALL_BIN)/{$(REDIS_SERVER_NAME),$(REDIS_BENCHMARK_NAME),$(REDIS_CLI_NAME),$(REDIS_CHECK_RDB_NAME),$(REDIS_CHECK_AOF_NAME),$(REDIS_SENTINEL_NAME)} + rm -f $(INSTALL_BIN)/{$(REDIS_SERVER_NAME),$(REDIS_BENCHMARK_NAME),$(REDIS_CLI_NAME),$(REDIS_CHECK_RDB_NAME),$(REDIS_CHECK_AOF_NAME),$(REDIS_SENTINEL_NAME),$(REDIS_DIAGNOSTIC_NAME)} diff --git a/src/keydb-diagnostic-tool.cpp b/src/keydb-diagnostic-tool.cpp new file mode 100644 index 000000000..8dea6cdbf --- /dev/null +++ b/src/keydb-diagnostic-tool.cpp @@ -0,0 +1,1830 @@ +/* KeyDB diagnostic utility. + * + * Copyright (c) 2009-2012, Salvatore Sanfilippo + * 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. + */ + +#include "fmacros.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +extern "C" { +#include /* Use hiredis sds. */ +#include "hiredis.h" +} +#include "ae.h" +#include "adlist.h" +#include "dict.h" +#include "zmalloc.h" +#include "storage.h" +#include "atomicvar.h" +#include "crc16_slottable.h" + +#define UNUSED(V) ((void) V) +#define RANDPTR_INITIAL_SIZE 8 +#define MAX_LATENCY_PRECISION 3 +#define MAX_THREADS 500 +#define CLUSTER_SLOTS 16384 + +#define CLIENT_GET_EVENTLOOP(c) \ + (c->thread_id >= 0 ? config.threads[c->thread_id]->el : config.el) + +struct benchmarkThread; +struct clusterNode; +struct redisConfig; + +int g_fTestMode = false; + +static struct config { + aeEventLoop *el; + const char *hostip; + int hostport; + const char *hostsocket; + int numclients; + int liveclients; + int requests; + int requests_issued; + int requests_finished; + int keysize; + int datasize; + int randomkeys; + int randomkeys_keyspacelen; + int keepalive; + int pipeline; + int showerrors; + long long start; + long long totlatency; + long long *latency; + const char *title; + list *clients; + int quiet; + int csv; + int loop; + int idlemode; + int dbnum; + sds dbnumstr; + char *tests; + char *auth; + const char *user; + int precision; + int num_threads; + struct benchmarkThread **threads; + int cluster_mode; + int cluster_node_count; + struct clusterNode **cluster_nodes; + struct redisConfig *redis_config; + int is_fetching_slots; + int is_updating_slots; + int slots_last_update; + int enable_tracking; + /* Thread mutexes to be used as fallbacks by atomicvar.h */ + pthread_mutex_t requests_issued_mutex; + pthread_mutex_t requests_finished_mutex; + pthread_mutex_t liveclients_mutex; + pthread_mutex_t is_fetching_slots_mutex; + pthread_mutex_t is_updating_slots_mutex; + pthread_mutex_t updating_slots_mutex; + pthread_mutex_t slots_last_update_mutex; +} config; + +typedef struct _client { + redisContext *context; + sds obuf; + char **randptr; /* Pointers to :rand: strings inside the command buf */ + size_t randlen; /* Number of pointers in client->randptr */ + size_t randfree; /* Number of unused pointers in client->randptr */ + char **stagptr; /* Pointers to slot hashtags (cluster mode only) */ + size_t staglen; /* Number of pointers in client->stagptr */ + size_t stagfree; /* Number of unused pointers in client->stagptr */ + size_t written; /* Bytes of 'obuf' already written */ + long long start; /* Start time of a request */ + long long latency; /* Request latency */ + int pending; /* Number of pending requests (replies to consume) */ + int prefix_pending; /* If non-zero, number of pending prefix commands. Commands + such as auth and select are prefixed to the pipeline of + benchmark commands and discarded after the first send. */ + int prefixlen; /* Size in bytes of the pending prefix commands */ + int thread_id; + struct clusterNode *cluster_node; + int slots_last_update; +} *client; + +/* Threads. */ + +typedef struct benchmarkThread { + int index; + pthread_t thread; + aeEventLoop *el; +} benchmarkThread; + +/* Cluster. */ +typedef struct clusterNode { + char *ip; + int port; + sds name; + int flags; + sds replicate; /* Master ID if node is a replica */ + int *slots; + int slots_count; + int current_slot_index; + int *updated_slots; /* Used by updateClusterSlotsConfiguration */ + int updated_slots_count; /* Used by updateClusterSlotsConfiguration */ + int replicas_count; + sds *migrating; /* An array of sds where even strings are slots and odd + * strings are the destination node IDs. */ + sds *importing; /* An array of sds where even strings are slots and odd + * strings are the source node IDs. */ + int migrating_count; /* Length of the migrating array (migrating slots*2) */ + int importing_count; /* Length of the importing array (importing slots*2) */ + struct redisConfig *redis_config; +} clusterNode; + +typedef struct redisConfig { + sds save; + sds appendonly; +} redisConfig; + +int g_fInCrash = false; + +/* Prototypes */ +static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask); +static void createMissingClients(client c); +static benchmarkThread *createBenchmarkThread(int index); +static void freeBenchmarkThread(benchmarkThread *thread); +static void freeBenchmarkThreads(); +static void *execBenchmarkThread(void *ptr); +static clusterNode *createClusterNode(char *ip, int port); +static redisConfig *getRedisConfig(const char *ip, int port, + const char *hostsocket); +static redisContext *getRedisContext(const char *ip, int port, + const char *hostsocket); +static void freeRedisConfig(redisConfig *cfg); +static int fetchClusterSlotsConfiguration(client c); +static void updateClusterSlotsConfiguration(); +int showThroughput(struct aeEventLoop *eventLoop, long long id, + void *clientData); + +/* Dict callbacks */ +static uint64_t dictSdsHash(const void *key); +static int dictSdsKeyCompare(void *privdata, const void *key1, + const void *key2); + +/* Implementation */ +static long long ustime(void) { + struct timeval tv; + long long ust; + + gettimeofday(&tv, NULL); + ust = ((long)tv.tv_sec)*1000000; + ust += tv.tv_usec; + return ust; +} + +static long long mstime(void) { + struct timeval tv; + long long mst; + + gettimeofday(&tv, NULL); + mst = ((long long)tv.tv_sec)*1000; + mst += tv.tv_usec/1000; + return mst; +} + +static uint64_t dictSdsHash(const void *key) { + return dictGenHashFunction((unsigned char*)key, sdslen((char*)key)); +} + +static int dictSdsKeyCompare(void *privdata, const void *key1, + const void *key2) +{ + int l1,l2; + DICT_NOTUSED(privdata); + + l1 = sdslen((sds)key1); + l2 = sdslen((sds)key2); + if (l1 != l2) return 0; + return memcmp(key1, key2, l1) == 0; +} + +/* _serverAssert is needed by dict */ +extern "C" void _serverAssert(const char *estr, const char *file, int line) { + fprintf(stderr, "=== ASSERTION FAILED ==="); + fprintf(stderr, "==> %s:%d '%s' is not true",file,line,estr); + *((char*)-1) = 'x'; +} + +static redisContext *getRedisContext(const char *ip, int port, + const char *hostsocket) +{ + redisContext *ctx = NULL; + redisReply *reply = NULL; + if (hostsocket == NULL) + ctx = redisConnect(ip, port); + else + ctx = redisConnectUnix(hostsocket); + if (ctx == NULL || ctx->err) { + fprintf(stderr,"Could not connect to Redis at "); + const char *err = (ctx != NULL ? ctx->errstr : ""); + if (hostsocket == NULL) + fprintf(stderr,"%s:%d: %s\n",ip,port,err); + else + fprintf(stderr,"%s: %s\n",hostsocket,err); + goto cleanup; + } + if (config.auth == NULL) + return ctx; + if (config.user == NULL) + reply = (redisReply*)redisCommand(ctx,"AUTH %s", config.auth); + else + reply = (redisReply*)redisCommand(ctx,"AUTH %s %s", config.user, config.auth); + if (reply != NULL) { + if (reply->type == REDIS_REPLY_ERROR) { + if (hostsocket == NULL) + fprintf(stderr, "Node %s:%d replied with error:\n%s\n", ip, port, reply->str); + else + fprintf(stderr, "Node %s replied with error:\n%s\n", hostsocket, reply->str); + goto cleanup; + } + freeReplyObject(reply); + return ctx; + } + fprintf(stderr, "ERROR: failed to fetch reply from "); + if (hostsocket == NULL) + fprintf(stderr, "%s:%d\n", ip, port); + else + fprintf(stderr, "%s\n", hostsocket); +cleanup: + freeReplyObject(reply); + redisFree(ctx); + return NULL; +} + +static redisConfig *getRedisConfig(const char *ip, int port, + const char *hostsocket) +{ + redisConfig *cfg = (redisConfig*)zcalloc(sizeof(*cfg)); + if (!cfg) return NULL; + redisContext *c = NULL; + redisReply *reply = NULL, *sub_reply = NULL; + c = getRedisContext(ip, port, hostsocket); + if (c == NULL) { + freeRedisConfig(cfg); + return NULL; + } + redisAppendCommand(c, "CONFIG GET %s", "save"); + redisAppendCommand(c, "CONFIG GET %s", "appendonly"); + + void *r; + for (int i=0; i < 2; i++) { + int res = redisGetReply(c, &r); + if (reply) freeReplyObject(reply); + reply = res == REDIS_OK ? ((redisReply *) r) : NULL; + if (res != REDIS_OK || !r) goto fail; + if (reply->type == REDIS_REPLY_ERROR) { + fprintf(stderr, "ERROR: %s\n", reply->str); + goto fail; + } + if (reply->type != REDIS_REPLY_ARRAY || reply->elements < 2) goto fail; + sub_reply = reply->element[1]; + const char *value = sub_reply->str; + if (!value) value = ""; + switch (i) { + case 0: cfg->save = sdsnew(value); break; + case 1: cfg->appendonly = sdsnew(value); break; + } + } + freeReplyObject(reply); + redisFree(c); + return cfg; +fail: + fprintf(stderr, "ERROR: failed to fetch CONFIG from "); + if (hostsocket == NULL) fprintf(stderr, "%s:%d\n", ip, port); + else fprintf(stderr, "%s\n", hostsocket); + freeReplyObject(reply); + redisFree(c); + freeRedisConfig(cfg); + return NULL; +} +static void freeRedisConfig(redisConfig *cfg) { + if (cfg->save) sdsfree(cfg->save); + if (cfg->appendonly) sdsfree(cfg->appendonly); + zfree(cfg); +} + +static void freeClient(client c) { + aeEventLoop *el = CLIENT_GET_EVENTLOOP(c); + listNode *ln; + aeDeleteFileEvent(el,c->context->fd,AE_WRITABLE); + aeDeleteFileEvent(el,c->context->fd,AE_READABLE); + if (c->thread_id >= 0) { + int requests_finished = 0; + atomicGet(config.requests_finished, requests_finished); + if (requests_finished >= config.requests) { + aeStop(el); + } + } + redisFree(c->context); + sdsfree(c->obuf); + zfree(c->randptr); + zfree(c->stagptr); + zfree(c); + if (config.num_threads) pthread_mutex_lock(&(config.liveclients_mutex)); + config.liveclients--; + ln = listSearchKey(config.clients,c); + assert(ln != NULL); + listDelNode(config.clients,ln); + if (config.num_threads) pthread_mutex_unlock(&(config.liveclients_mutex)); +} + +static void freeAllClients(void) { + listNode *ln = config.clients->head, *next; + + while(ln) { + next = ln->next; + freeClient((client)ln->value); + ln = next; + } +} + +static void resetClient(client c) { + aeEventLoop *el = CLIENT_GET_EVENTLOOP(c); + aeDeleteFileEvent(el,c->context->fd,AE_WRITABLE); + aeDeleteFileEvent(el,c->context->fd,AE_READABLE); + aeCreateFileEvent(el,c->context->fd,AE_WRITABLE,writeHandler,c); + c->written = 0; + c->pending = config.pipeline; +} + +static void randomizeClientKey(client c) { + size_t i; + + for (i = 0; i < c->randlen; i++) { + char *p = c->randptr[i]+11; + size_t r = 0; + if (config.randomkeys_keyspacelen != 0) + r = random() % config.randomkeys_keyspacelen; + size_t j; + + for (j = 0; j < 12; j++) { + *p = '0'+r%10; + r/=10; + p--; + } + } +} + +static void setClusterKeyHashTag(client c) { + assert(c->thread_id >= 0); + clusterNode *node = c->cluster_node; + assert(node); + assert(node->current_slot_index < node->slots_count); + int is_updating_slots = 0; + atomicGet(config.is_updating_slots, is_updating_slots); + /* If updateClusterSlotsConfiguration is updating the slots array, + * call updateClusterSlotsConfiguration is order to block the thread + * since the mutex is locked. When the slots will be updated by the + * thread that's actually performing the update, the execution of + * updateClusterSlotsConfiguration won't actually do anything, since + * the updated_slots_count array will be already NULL. */ + if (is_updating_slots) updateClusterSlotsConfiguration(); + int slot = node->slots[node->current_slot_index]; + const char *tag = crc16_slot_table[slot]; + int taglen = strlen(tag); + size_t i; + for (i = 0; i < c->staglen; i++) { + char *p = c->stagptr[i] + 1; + p[0] = tag[0]; + p[1] = (taglen >= 2 ? tag[1] : '}'); + p[2] = (taglen == 3 ? tag[2] : '}'); + } +} + +static void clientDone(client c) { + int requests_finished = 0; + atomicGet(config.requests_finished, requests_finished); + if (requests_finished >= config.requests) { + freeClient(c); + if (!config.num_threads && config.el) aeStop(config.el); + return; + } + if (config.keepalive) { + resetClient(c); + } else { + if (config.num_threads) pthread_mutex_lock(&(config.liveclients_mutex)); + config.liveclients--; + createMissingClients(c); + config.liveclients++; + if (config.num_threads) + pthread_mutex_unlock(&(config.liveclients_mutex)); + freeClient(c); + } +} + +static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) { + client c = (client)privdata; + void *reply = NULL; + UNUSED(el); + UNUSED(fd); + UNUSED(mask); + + /* Calculate latency only for the first read event. This means that the + * server already sent the reply and we need to parse it. Parsing overhead + * is not part of the latency, so calculate it only once, here. */ + if (c->latency < 0) c->latency = ustime()-(c->start); + + if (redisBufferRead(c->context) != REDIS_OK) { + fprintf(stderr,"Error: %s\n",c->context->errstr); + exit(1); + } else { + while(c->pending) { + if (redisGetReply(c->context,&reply) != REDIS_OK) { + fprintf(stderr,"Error: %s\n",c->context->errstr); + exit(1); + } + if (reply != NULL) { + if (reply == (void*)REDIS_REPLY_ERROR) { + fprintf(stderr,"Unexpected error reply, exiting...\n"); + exit(1); + } + redisReply *r = (redisReply*)reply; + int is_err = (r->type == REDIS_REPLY_ERROR); + + if (is_err && config.showerrors) { + /* TODO: static lasterr_time not thread-safe */ + static time_t lasterr_time = 0; + time_t now = time(NULL); + if (lasterr_time != now) { + lasterr_time = now; + if (c->cluster_node) { + printf("Error from server %s:%d: %s\n", + c->cluster_node->ip, + c->cluster_node->port, + r->str); + } else printf("Error from server: %s\n", r->str); + } + } + + /* Try to update slots configuration if reply error is + * MOVED/ASK/CLUSTERDOWN and the key(s) used by the command + * contain(s) the slot hash tag. */ + if (is_err && c->cluster_node && c->staglen) { + int fetch_slots = 0, do_wait = 0; + if (!strncmp(r->str,"MOVED",5) || !strncmp(r->str,"ASK",3)) + fetch_slots = 1; + else if (!strncmp(r->str,"CLUSTERDOWN",11)) { + /* Usually the cluster is able to recover itself after + * a CLUSTERDOWN error, so try to sleep one second + * before requesting the new configuration. */ + fetch_slots = 1; + do_wait = 1; + printf("Error from server %s:%d: %s\n", + c->cluster_node->ip, + c->cluster_node->port, + r->str); + } + if (do_wait) sleep(1); + if (fetch_slots && !fetchClusterSlotsConfiguration(c)) + exit(1); + } + + freeReplyObject(reply); + /* This is an OK for prefix commands such as auth and select.*/ + if (c->prefix_pending > 0) { + c->prefix_pending--; + c->pending--; + /* Discard prefix commands on first response.*/ + if (c->prefixlen > 0) { + size_t j; + sdsrange(c->obuf, c->prefixlen, -1); + /* We also need to fix the pointers to the strings + * we need to randomize. */ + for (j = 0; j < c->randlen; j++) + c->randptr[j] -= c->prefixlen; + c->prefixlen = 0; + } + continue; + } + int requests_finished = 0; + atomicGetIncr(config.requests_finished, requests_finished, 1); + if (requests_finished < config.requests) + config.latency[requests_finished] = c->latency; + c->pending--; + if (c->pending == 0) { + clientDone(c); + break; + } + } else { + break; + } + } + } +} + +static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) { + client c = (client)privdata; + UNUSED(el); + UNUSED(fd); + UNUSED(mask); + + /* Initialize request when nothing was written. */ + if (c->written == 0) { + /* Enforce upper bound to number of requests. */ + int requests_issued = 0; + atomicGetIncr(config.requests_issued, requests_issued, 1); + if (requests_issued >= config.requests) { + freeClient(c); + return; + } + + /* Really initialize: randomize keys and set start time. */ + if (config.randomkeys) randomizeClientKey(c); + if (config.cluster_mode && c->staglen > 0) setClusterKeyHashTag(c); + atomicGet(config.slots_last_update, c->slots_last_update); + c->start = ustime(); + c->latency = -1; + } + if (sdslen(c->obuf) > c->written) { + void *ptr = c->obuf+c->written; + ssize_t nwritten = write(c->context->fd,ptr,sdslen(c->obuf)-c->written); + if (nwritten == -1) { + if (errno != EPIPE) + fprintf(stderr, "Writing to socket: %s\n", strerror(errno)); + freeClient(c); + return; + } + c->written += nwritten; + if (sdslen(c->obuf) == c->written) { + aeDeleteFileEvent(el,c->context->fd,AE_WRITABLE); + aeCreateFileEvent(el,c->context->fd,AE_READABLE,readHandler,c); + } + } +} + +/* Create a benchmark client, configured to send the command passed as 'cmd' of + * 'len' bytes. + * + * The command is copied N times in the client output buffer (that is reused + * again and again to send the request to the server) accordingly to the configured + * pipeline size. + * + * Also an initial SELECT command is prepended in order to make sure the right + * database is selected, if needed. The initial SELECT will be discarded as soon + * as the first reply is received. + * + * To create a client from scratch, the 'from' pointer is set to NULL. If instead + * we want to create a client using another client as reference, the 'from' pointer + * points to the client to use as reference. In such a case the following + * information is take from the 'from' client: + * + * 1) The command line to use. + * 2) The offsets of the __rand_int__ elements inside the command line, used + * for arguments randomization. + * + * Even when cloning another client, prefix commands are applied if needed.*/ +static client createClient(const char *cmd, size_t len, client from, int thread_id) { + int j; + int is_cluster_client = (config.cluster_mode && thread_id >= 0); + client c = (client)zmalloc(sizeof(struct _client), MALLOC_LOCAL); + + const char *ip = NULL; + int port = 0; + c->cluster_node = NULL; + if (config.hostsocket == NULL || is_cluster_client) { + if (!is_cluster_client) { + ip = config.hostip; + port = config.hostport; + } else { + int node_idx = 0; + if (config.num_threads < config.cluster_node_count) + node_idx = config.liveclients % config.cluster_node_count; + else + node_idx = thread_id % config.cluster_node_count; + clusterNode *node = config.cluster_nodes[node_idx]; + assert(node != NULL); + ip = (const char *) node->ip; + port = node->port; + c->cluster_node = node; + } + c->context = redisConnectNonBlock(ip,port); + } else { + c->context = redisConnectUnixNonBlock(config.hostsocket); + } + if (c->context->err) { + fprintf(stderr,"Could not connect to Redis at "); + if (config.hostsocket == NULL || is_cluster_client) + fprintf(stderr,"%s:%d: %s\n",ip,port,c->context->errstr); + else + fprintf(stderr,"%s: %s\n",config.hostsocket,c->context->errstr); + exit(1); + } + c->thread_id = thread_id; + /* Suppress hiredis cleanup of unused buffers for max speed. */ + c->context->reader->maxbuf = 0; + + /* Build the request buffer: + * Queue N requests accordingly to the pipeline size, or simply clone + * the example client buffer. */ + c->obuf = sdsempty(); + /* Prefix the request buffer with AUTH and/or SELECT commands, if applicable. + * These commands are discarded after the first response, so if the client is + * reused the commands will not be used again. */ + c->prefix_pending = 0; + if (config.auth) { + char *buf = NULL; + int len; + if (config.user == NULL) + len = redisFormatCommand(&buf, "AUTH %s", config.auth); + else + len = redisFormatCommand(&buf, "AUTH %s %s", + config.user, config.auth); + c->obuf = sdscatlen(c->obuf, buf, len); + free(buf); + c->prefix_pending++; + } + + if (config.enable_tracking) { + char *buf = NULL; + int len = redisFormatCommand(&buf, "CLIENT TRACKING on"); + c->obuf = sdscatlen(c->obuf, buf, len); + free(buf); + c->prefix_pending++; + } + + /* If a DB number different than zero is selected, prefix our request + * buffer with the SELECT command, that will be discarded the first + * time the replies are received, so if the client is reused the + * SELECT command will not be used again. */ + if (config.dbnum != 0 && !is_cluster_client) { + c->obuf = sdscatprintf(c->obuf,"*2\r\n$6\r\nSELECT\r\n$%d\r\n%s\r\n", + (int)sdslen(config.dbnumstr),config.dbnumstr); + c->prefix_pending++; + } + c->prefixlen = sdslen(c->obuf); + /* Append the request itself. */ + if (from) { + c->obuf = sdscatlen(c->obuf, + from->obuf+from->prefixlen, + sdslen(from->obuf)-from->prefixlen); + } else { + for (j = 0; j < config.pipeline; j++) + c->obuf = sdscatlen(c->obuf,cmd,len); + } + + c->written = 0; + c->pending = config.pipeline+c->prefix_pending; + c->randptr = NULL; + c->randlen = 0; + c->stagptr = NULL; + c->staglen = 0; + + /* Find substrings in the output buffer that need to be randomized. */ + if (config.randomkeys) { + if (from) { + c->randlen = from->randlen; + c->randfree = 0; + c->randptr = (char**)zmalloc(sizeof(char*)*c->randlen, MALLOC_LOCAL); + /* copy the offsets. */ + for (j = 0; j < (int)c->randlen; j++) { + c->randptr[j] = c->obuf + (from->randptr[j]-from->obuf); + /* Adjust for the different select prefix length. */ + c->randptr[j] += c->prefixlen - from->prefixlen; + } + } else { + char *p = c->obuf; + + c->randlen = 0; + c->randfree = RANDPTR_INITIAL_SIZE; + c->randptr = (char**)zmalloc(sizeof(char*)*c->randfree, MALLOC_LOCAL); + while ((p = strstr(p,"__rand_int__")) != NULL) { + if (c->randfree == 0) { + c->randptr = (char**)zrealloc(c->randptr,sizeof(char*)*c->randlen*2, MALLOC_LOCAL); + c->randfree += c->randlen; + } + c->randptr[c->randlen++] = p; + c->randfree--; + p += 12; /* 12 is strlen("__rand_int__). */ + } + } + } + /* If cluster mode is enabled, set slot hashtags pointers. */ + if (config.cluster_mode) { + if (from) { + c->staglen = from->staglen; + c->stagfree = 0; + c->stagptr = (char**)zmalloc(sizeof(char*)*c->staglen, MALLOC_LOCAL); + /* copy the offsets. */ + for (j = 0; j < (int)c->staglen; j++) { + c->stagptr[j] = c->obuf + (from->stagptr[j]-from->obuf); + /* Adjust for the different select prefix length. */ + c->stagptr[j] += c->prefixlen - from->prefixlen; + } + } else { + char *p = c->obuf; + + c->staglen = 0; + c->stagfree = RANDPTR_INITIAL_SIZE; + c->stagptr = (char**)zmalloc(sizeof(char*)*c->stagfree, MALLOC_LOCAL); + while ((p = strstr(p,"{tag}")) != NULL) { + if (c->stagfree == 0) { + c->stagptr = (char**)zrealloc(c->stagptr, + sizeof(char*) * c->staglen*2, MALLOC_LOCAL); + c->stagfree += c->staglen; + } + c->stagptr[c->staglen++] = p; + c->stagfree--; + p += 5; /* 5 is strlen("{tag}"). */ + } + } + } + aeEventLoop *el = NULL; + if (thread_id < 0) el = config.el; + else { + benchmarkThread *thread = config.threads[thread_id]; + el = thread->el; + } + if (config.idlemode == 0) + aeCreateFileEvent(el,c->context->fd,AE_WRITABLE,writeHandler,c); + listAddNodeTail(config.clients,c); + atomicIncr(config.liveclients, 1); + atomicGet(config.slots_last_update, c->slots_last_update); + return c; +} + +static void createMissingClients(client c) { + int n = 0; + while(config.liveclients < config.numclients) { + int thread_id = -1; + if (config.num_threads) + thread_id = config.liveclients % config.num_threads; + createClient(NULL,0,c,thread_id); + + /* Listen backlog is quite limited on most systems */ + if (++n > 64) { + usleep(50000); + n = 0; + } + } +} + +static int compareLatency(const void *a, const void *b) { + return (*(long long*)a)-(*(long long*)b); +} + +static int ipow(int base, int exp) { + int result = 1; + while (exp) { + if (exp & 1) result *= base; + exp /= 2; + base *= base; + } + return result; +} + +static void showLatencyReport(void) { + int i, curlat = 0; + int usbetweenlat = ipow(10, MAX_LATENCY_PRECISION-config.precision); + float perc, reqpersec; + + reqpersec = (float)config.requests_finished/((float)config.totlatency/1000); + if (!config.quiet && !config.csv) { + printf("====== %s ======\n", config.title); + printf(" %d requests completed in %.2f seconds\n", config.requests_finished, + (float)config.totlatency/1000); + printf(" %d parallel clients\n", config.numclients); + printf(" %d bytes payload\n", config.datasize); + printf(" keep alive: %d\n", config.keepalive); + if (config.cluster_mode) { + printf(" cluster mode: yes (%d masters)\n", + config.cluster_node_count); + int m ; + for (m = 0; m < config.cluster_node_count; m++) { + clusterNode *node = config.cluster_nodes[m]; + redisConfig *cfg = node->redis_config; + if (cfg == NULL) continue; + printf(" node [%d] configuration:\n",m ); + printf(" save: %s\n", + sdslen(cfg->save) ? cfg->save : "NONE"); + printf(" appendonly: %s\n", cfg->appendonly); + } + } else { + if (config.redis_config) { + printf(" host configuration \"save\": %s\n", + config.redis_config->save); + printf(" host configuration \"appendonly\": %s\n", + config.redis_config->appendonly); + } + } + printf(" multi-thread: %s\n", (config.num_threads ? "yes" : "no")); + if (config.num_threads) + printf(" threads: %d\n", config.num_threads); + + printf("\n"); + + qsort(config.latency,config.requests,sizeof(long long),compareLatency); + for (i = 0; i < config.requests; i++) { + if (config.latency[i]/usbetweenlat != curlat || + i == (config.requests-1)) + { + /* After the 2 milliseconds latency to have percentages split + * by decimals will just add a lot of noise to the output. */ + if (config.latency[i] >= 2000) { + config.precision = 0; + usbetweenlat = ipow(10, + MAX_LATENCY_PRECISION-config.precision); + } + + curlat = config.latency[i]/usbetweenlat; + perc = ((float)(i+1)*100)/config.requests; + printf("%.2f%% <= %.*f milliseconds\n", perc, config.precision, + curlat/pow(10.0, config.precision)); + } + } + printf("%.2f requests per second\n\n", reqpersec); + } else if (config.csv) { + printf("\"%s\",\"%.2f\"\n", config.title, reqpersec); + } else { + printf("%s: %.2f requests per second\n", config.title, reqpersec); + } +} + +static void initBenchmarkThreads() { + int i; + if (config.threads) freeBenchmarkThreads(); + config.threads = (benchmarkThread**)zmalloc(config.num_threads * sizeof(benchmarkThread*), MALLOC_LOCAL); + for (i = 0; i < config.num_threads; i++) { + benchmarkThread *thread = createBenchmarkThread(i); + config.threads[i] = thread; + } +} + +static void startBenchmarkThreads() { + int i; + for (i = 0; i < config.num_threads; i++) { + benchmarkThread *t = config.threads[i]; + if (pthread_create(&(t->thread), NULL, execBenchmarkThread, t)){ + fprintf(stderr, "FATAL: Failed to start thread %d.\n", i); + exit(1); + } + } + for (i = 0; i < config.num_threads; i++) + pthread_join(config.threads[i]->thread, NULL); +} + +static void benchmark(const char *title, const char *cmd, int len) { + client c; + + config.title = title; + config.requests_issued = 0; + config.requests_finished = 0; + + if (config.num_threads) initBenchmarkThreads(); + + int thread_id = config.num_threads > 0 ? 0 : -1; + c = createClient(cmd,len,NULL,thread_id); + createMissingClients(c); + + config.start = mstime(); + if (!config.num_threads) aeMain(config.el); + else startBenchmarkThreads(); + config.totlatency = mstime()-config.start; + + showLatencyReport(); + freeAllClients(); + if (config.threads) freeBenchmarkThreads(); +} + +/* Thread functions. */ + +static benchmarkThread *createBenchmarkThread(int index) { + benchmarkThread *thread = (benchmarkThread*)zmalloc(sizeof(*thread), MALLOC_LOCAL); + if (thread == NULL) return NULL; + thread->index = index; + thread->el = aeCreateEventLoop(1024*10); + aeCreateTimeEvent(thread->el,1,showThroughput,NULL,NULL); + return thread; +} + +static void freeBenchmarkThread(benchmarkThread *thread) { + if (thread->el) aeDeleteEventLoop(thread->el); + zfree(thread); +} + +static void freeBenchmarkThreads() { + int i = 0; + for (; i < config.num_threads; i++) { + benchmarkThread *thread = config.threads[i]; + if (thread) freeBenchmarkThread(thread); + } + zfree(config.threads); + config.threads = NULL; +} + +static void *execBenchmarkThread(void *ptr) { + benchmarkThread *thread = (benchmarkThread *) ptr; + aeMain(thread->el); + return NULL; +} + +/* Cluster helper functions. */ + +static clusterNode *createClusterNode(char *ip, int port) { + clusterNode *node = (clusterNode*)zmalloc(sizeof(*node), MALLOC_LOCAL); + if (!node) return NULL; + node->ip = ip; + node->port = port; + node->name = NULL; + node->flags = 0; + node->replicate = NULL; + node->replicas_count = 0; + node->slots = (int*)zmalloc(CLUSTER_SLOTS * sizeof(int), MALLOC_LOCAL); + node->slots_count = 0; + node->current_slot_index = 0; + node->updated_slots = NULL; + node->updated_slots_count = 0; + node->migrating = NULL; + node->importing = NULL; + node->migrating_count = 0; + node->importing_count = 0; + node->redis_config = NULL; + return node; +} + +static void freeClusterNode(clusterNode *node) { + int i; + if (node->name) sdsfree(node->name); + if (node->replicate) sdsfree(node->replicate); + if (node->migrating != NULL) { + for (i = 0; i < node->migrating_count; i++) sdsfree(node->migrating[i]); + zfree(node->migrating); + } + if (node->importing != NULL) { + for (i = 0; i < node->importing_count; i++) sdsfree(node->importing[i]); + zfree(node->importing); + } + /* If the node is not the reference node, that uses the address from + * config.hostip and config.hostport, then the node ip has been + * allocated by fetchClusterConfiguration, so it must be freed. */ + if (node->ip && strcmp(node->ip, config.hostip) != 0) sdsfree(node->ip); + if (node->redis_config != NULL) freeRedisConfig(node->redis_config); + zfree(node->slots); + zfree(node); +} + +static void freeClusterNodes() { + int i = 0; + for (; i < config.cluster_node_count; i++) { + clusterNode *n = config.cluster_nodes[i]; + if (n) freeClusterNode(n); + } + zfree(config.cluster_nodes); + config.cluster_nodes = NULL; +} + +static clusterNode **addClusterNode(clusterNode *node) { + int count = config.cluster_node_count + 1; + config.cluster_nodes = (clusterNode**)zrealloc(config.cluster_nodes, + count * sizeof(*node), MALLOC_LOCAL); + if (!config.cluster_nodes) return NULL; + config.cluster_nodes[config.cluster_node_count++] = node; + return config.cluster_nodes; +} + +static int fetchClusterConfiguration() { + int success = 1; + redisContext *ctx = NULL; + redisReply *reply = NULL; + char *lines = NULL; + char *line = NULL; + char *p = NULL; + ctx = getRedisContext(config.hostip, config.hostport, config.hostsocket); + if (ctx == NULL) { + exit(1); + } + clusterNode *firstNode = createClusterNode((char *) config.hostip, + config.hostport); + if (!firstNode) {success = 0; goto cleanup;} + reply = (redisReply*)redisCommand(ctx, "CLUSTER NODES"); + success = (reply != NULL); + if (!success) goto cleanup; + success = (reply->type != REDIS_REPLY_ERROR); + if (!success) { + if (config.hostsocket == NULL) { + fprintf(stderr, "Cluster node %s:%d replied with error:\n%s\n", + config.hostip, config.hostport, reply->str); + } else { + fprintf(stderr, "Cluster node %s replied with error:\n%s\n", + config.hostsocket, reply->str); + } + goto cleanup; + } + lines = reply->str; + while ((p = strstr(lines, "\n")) != NULL) { + *p = '\0'; + line = lines; + lines = p + 1; + char *name = NULL, *addr = NULL, *flags = NULL, *master_id = NULL; + int i = 0; + while ((p = strchr(line, ' ')) != NULL) { + *p = '\0'; + char *token = line; + line = p + 1; + switch(i++){ + case 0: name = token; break; + case 1: addr = token; break; + case 2: flags = token; break; + case 3: master_id = token; break; + } + if (i == 8) break; // Slots + } + if (!flags) { + fprintf(stderr, "Invalid CLUSTER NODES reply: missing flags.\n"); + success = 0; + goto cleanup; + } + int myself = (strstr(flags, "myself") != NULL); + int is_replica = (strstr(flags, "slave") != NULL || + (master_id != NULL && master_id[0] != '-')); + if (is_replica) continue; + if (addr == NULL) { + fprintf(stderr, "Invalid CLUSTER NODES reply: missing addr.\n"); + success = 0; + goto cleanup; + } + clusterNode *node = NULL; + char *ip = NULL; + int port = 0; + char *paddr = strchr(addr, ':'); + if (paddr != NULL) { + *paddr = '\0'; + ip = addr; + addr = paddr + 1; + /* If internal bus is specified, then just drop it. */ + if ((paddr = strchr(addr, '@')) != NULL) *paddr = '\0'; + port = atoi(addr); + } + if (myself) { + node = firstNode; + if (node->ip == NULL && ip != NULL) { + node->ip = ip; + node->port = port; + } + } else { + node = createClusterNode(sdsnew(ip), port); + } + if (node == NULL) { + success = 0; + goto cleanup; + } + if (name != NULL) node->name = sdsnew(name); + if (i == 8) { + int remaining = strlen(line); + while (remaining > 0) { + p = strchr(line, ' '); + if (p == NULL) p = line + remaining; + remaining -= (p - line); + + char *slotsdef = line; + *p = '\0'; + if (remaining) { + line = p + 1; + remaining--; + } else line = p; + char *dash = NULL; + if (slotsdef[0] == '[') { + slotsdef++; + if ((p = strstr(slotsdef, "->-"))) { // Migrating + *p = '\0'; + p += 3; + char *closing_bracket = strchr(p, ']'); + if (closing_bracket) *closing_bracket = '\0'; + sds slot = sdsnew(slotsdef); + sds dst = sdsnew(p); + node->migrating_count += 2; + node->migrating = + (char**)zrealloc(node->migrating, + (node->migrating_count * sizeof(sds)), MALLOC_LOCAL); + node->migrating[node->migrating_count - 2] = + slot; + node->migrating[node->migrating_count - 1] = + dst; + } else if ((p = strstr(slotsdef, "-<-"))) {//Importing + *p = '\0'; + p += 3; + char *closing_bracket = strchr(p, ']'); + if (closing_bracket) *closing_bracket = '\0'; + sds slot = sdsnew(slotsdef); + sds src = sdsnew(p); + node->importing_count += 2; + node->importing = (char**)zrealloc(node->importing, + (node->importing_count * sizeof(sds)), MALLOC_LOCAL); + node->importing[node->importing_count - 2] = + slot; + node->importing[node->importing_count - 1] = + src; + } + } else if ((dash = strchr(slotsdef, '-')) != NULL) { + p = dash; + int start, stop; + *p = '\0'; + start = atoi(slotsdef); + stop = atoi(p + 1); + while (start <= stop) { + int slot = start++; + node->slots[node->slots_count++] = slot; + } + } else if (p > slotsdef) { + int slot = atoi(slotsdef); + node->slots[node->slots_count++] = slot; + } + } + } + if (node->slots_count == 0) { + printf("WARNING: master node %s:%d has no slots, skipping...\n", + node->ip, node->port); + continue; + } + if (!addClusterNode(node)) { + success = 0; + goto cleanup; + } + } +cleanup: + if (ctx) redisFree(ctx); + if (!success) { + if (config.cluster_nodes) freeClusterNodes(); + } + if (reply) freeReplyObject(reply); + return success; +} + +/* Request the current cluster slots configuration by calling CLUSTER SLOTS + * and atomically update the slots after a successful reply. */ +static int fetchClusterSlotsConfiguration(client c) { + UNUSED(c); + int success = 1, is_fetching_slots = 0, last_update = 0; + size_t i; + atomicGet(config.slots_last_update, last_update); + if (c->slots_last_update < last_update) { + c->slots_last_update = last_update; + return -1; + } + redisReply *reply = NULL; + atomicGetIncr(config.is_fetching_slots, is_fetching_slots, 1); + if (is_fetching_slots) return -1; //TODO: use other codes || errno ? + atomicSet(config.is_fetching_slots, 1); + if (config.showerrors) + printf("Cluster slots configuration changed, fetching new one...\n"); + const char *errmsg = "Failed to update cluster slots configuration"; + static dictType dtype = { + dictSdsHash, /* hash function */ + NULL, /* key dup */ + NULL, /* val dup */ + dictSdsKeyCompare, /* key compare */ + NULL, /* key destructor */ + NULL /* val destructor */ + }; + /* printf("[%d] fetchClusterSlotsConfiguration\n", c->thread_id); */ + dict *masters = dictCreate(&dtype, NULL); + redisContext *ctx = NULL; + for (i = 0; i < (size_t) config.cluster_node_count; i++) { + clusterNode *node = config.cluster_nodes[i]; + assert(node->ip != NULL); + assert(node->name != NULL); + assert(node->port); + /* Use first node as entry point to connect to. */ + if (ctx == NULL) { + ctx = getRedisContext(node->ip, node->port, NULL); + if (!ctx) { + success = 0; + goto cleanup; + } + } + if (node->updated_slots != NULL) + zfree(node->updated_slots); + node->updated_slots = NULL; + node->updated_slots_count = 0; + dictReplace(masters, node->name, node) ; + } + reply = (redisReply*)redisCommand(ctx, "CLUSTER SLOTS"); + if (reply == NULL || reply->type == REDIS_REPLY_ERROR) { + success = 0; + if (reply) + fprintf(stderr,"%s\nCLUSTER SLOTS ERROR: %s\n",errmsg,reply->str); + goto cleanup; + } + assert(reply->type == REDIS_REPLY_ARRAY); + for (i = 0; i < reply->elements; i++) { + redisReply *r = reply->element[i]; + assert(r->type == REDIS_REPLY_ARRAY); + assert(r->elements >= 3); + int from, to, slot; + from = r->element[0]->integer; + to = r->element[1]->integer; + redisReply *nr = r->element[2]; + assert(nr->type == REDIS_REPLY_ARRAY && nr->elements >= 3); + assert(nr->element[2]->str != NULL); + sds name = sdsnew(nr->element[2]->str); + dictEntry *entry = dictFind(masters, name); + if (entry == NULL) { + success = 0; + fprintf(stderr, "%s: could not find node with ID %s in current " + "configuration.\n", errmsg, name); + if (name) sdsfree(name); + goto cleanup; + } + sdsfree(name); + clusterNode *node = (clusterNode*)dictGetVal(entry); + if (node->updated_slots == NULL) + node->updated_slots = (int*)zcalloc(CLUSTER_SLOTS * sizeof(int), MALLOC_LOCAL); + for (slot = from; slot <= to; slot++) + node->updated_slots[node->updated_slots_count++] = slot; + } + updateClusterSlotsConfiguration(); +cleanup: + freeReplyObject(reply); + redisFree(ctx); + dictRelease(masters); + atomicSet(config.is_fetching_slots, 0); + return success; +} + +/* Atomically update the new slots configuration. */ +static void updateClusterSlotsConfiguration() { + pthread_mutex_lock(&config.is_updating_slots_mutex); + atomicSet(config.is_updating_slots, 1); + int i; + for (i = 0; i < config.cluster_node_count; i++) { + clusterNode *node = config.cluster_nodes[i]; + if (node->updated_slots != NULL) { + int *oldslots = node->slots; + node->slots = node->updated_slots; + node->slots_count = node->updated_slots_count; + node->current_slot_index = 0; + node->updated_slots = NULL; + node->updated_slots_count = 0; + zfree(oldslots); + } + } + atomicSet(config.is_updating_slots, 0); + atomicIncr(config.slots_last_update, 1); + pthread_mutex_unlock(&config.is_updating_slots_mutex); +} + +/* Generate random data for redis benchmark. See #7196. */ +static void genBenchmarkRandomData(char *data, int count) { + static uint32_t state = 1234; + int i = 0; + + while (count--) { + state = (state*1103515245+12345); + data[i++] = '0'+((state>>16)&63); + } +} + +/* Returns number of consumed options. */ +int parseOptions(int argc, const char **argv) { + int i; + int lastarg; + int exit_status = 1; + + for (i = 1; i < argc; i++) { + lastarg = (i == (argc-1)); + + if (!strcmp(argv[i],"-c")) { + if (lastarg) goto invalid; + config.numclients = atoi(argv[++i]); + } else if (!strcmp(argv[i],"-n")) { + if (lastarg) goto invalid; + config.requests = atoi(argv[++i]); + } else if (!strcmp(argv[i],"-k")) { + if (lastarg) goto invalid; + config.keepalive = atoi(argv[++i]); + } else if (!strcmp(argv[i],"-h")) { + if (lastarg) goto invalid; + config.hostip = strdup(argv[++i]); + } else if (!strcmp(argv[i],"-p")) { + if (lastarg) goto invalid; + config.hostport = atoi(argv[++i]); + } else if (!strcmp(argv[i],"-s")) { + if (lastarg) goto invalid; + config.hostsocket = strdup(argv[++i]); + } else if (!strcmp(argv[i],"-a") ) { + if (lastarg) goto invalid; + config.auth = strdup(argv[++i]); + } else if (!strcmp(argv[i],"--user")) { + if (lastarg) goto invalid; + config.user = argv[++i]; + } else if (!strcmp(argv[i],"-d")) { + if (lastarg) goto invalid; + config.datasize = atoi(argv[++i]); + if (config.datasize < 1) config.datasize=1; + if (config.datasize > 1024*1024*1024) config.datasize = 1024*1024*1024; + } else if (!strcmp(argv[i],"-P")) { + if (lastarg) goto invalid; + config.pipeline = atoi(argv[++i]); + if (config.pipeline <= 0) config.pipeline=1; + } else if (!strcmp(argv[i],"-r")) { + if (lastarg) goto invalid; + const char *next = argv[++i], *p = next; + if (*p == '-') { + p++; + if (*p < '0' || *p > '9') goto invalid; + } + config.randomkeys = 1; + config.randomkeys_keyspacelen = atoi(next); + if (config.randomkeys_keyspacelen < 0) + config.randomkeys_keyspacelen = 0; + } else if (!strcmp(argv[i],"-q")) { + config.quiet = 1; + } else if (!strcmp(argv[i],"--csv")) { + config.csv = 1; + } else if (!strcmp(argv[i],"-l")) { + config.loop = 1; + } else if (!strcmp(argv[i],"-I")) { + config.idlemode = 1; + } else if (!strcmp(argv[i],"-e")) { + config.showerrors = 1; + } else if (!strcmp(argv[i],"-t")) { + if (lastarg) goto invalid; + /* We get the list of tests to run as a string in the form + * get,set,lrange,...,test_N. Then we add a comma before and + * after the string in order to make sure that searching + * for ",testname," will always get a match if the test is + * enabled. */ + config.tests = sdsnew(","); + config.tests = sdscat(config.tests,(char*)argv[++i]); + config.tests = sdscat(config.tests,","); + sdstolower(config.tests); + } else if (!strcmp(argv[i],"--dbnum")) { + if (lastarg) goto invalid; + config.dbnum = atoi(argv[++i]); + config.dbnumstr = sdsfromlonglong(config.dbnum); + } else if (!strcmp(argv[i],"--precision")) { + if (lastarg) goto invalid; + config.precision = atoi(argv[++i]); + if (config.precision < 0) config.precision = 0; + if (config.precision > MAX_LATENCY_PRECISION) config.precision = MAX_LATENCY_PRECISION; + } else if (!strcmp(argv[i],"--threads")) { + if (lastarg) goto invalid; + config.num_threads = atoi(argv[++i]); + if (config.num_threads > MAX_THREADS) { + printf("WARNING: too many threads, limiting threads to %d.\n", + MAX_THREADS); + config.num_threads = MAX_THREADS; + } else if (config.num_threads < 0) config.num_threads = 0; + } else if (!strcmp(argv[i],"--cluster")) { + config.cluster_mode = 1; + } else if (!strcmp(argv[i],"--enable-tracking")) { + config.enable_tracking = 1; + } else if (!strcmp(argv[i],"--help")) { + exit_status = 0; + goto usage; + } else { + /* Assume the user meant to provide an option when the arg starts + * with a dash. We're done otherwise and should use the remainder + * as the command and arguments for running the benchmark. */ + if (argv[i][0] == '-') goto invalid; + return i; + } + } + + return i; + +invalid: + printf("Invalid option \"%s\" or option argument missing\n\n",argv[i]); + +usage: + printf( +"Usage: keydb-benchmark [-h ] [-p ] [-c ] [-n ] [-k ]\n\n" +" -h Server hostname (default 127.0.0.1)\n" +" -p Server port (default 6379)\n" +" -s Server socket (overrides host and port)\n" +" -a Password for Redis Auth\n" +" --user Used to send ACL style 'AUTH username pass'. Needs -a.\n" +" -c Number of parallel connections (default 50)\n" +" -n Total number of requests (default 100000)\n" +" -d Data size of SET/GET value in bytes (default 3)\n" +" --dbnum SELECT the specified db number (default 0)\n" +" --threads Enable multi-thread mode.\n" +" --cluster Enable cluster mode.\n" +" --enable-tracking Send CLIENT TRACKING on before starting benchmark.\n" +" -k 1=keep alive 0=reconnect (default 1)\n" +" -r Use random keys for SET/GET/INCR, random values for SADD,\n" +" random members and scores for ZADD.\n" +" Using this option the benchmark will expand the string __rand_int__\n" +" inside an argument with a 12 digits number in the specified range\n" +" from 0 to keyspacelen-1. The substitution changes every time a command\n" +" is executed. Default tests use this to hit random keys in the\n" +" specified range.\n" +" -P Pipeline requests. Default 1 (no pipeline).\n" +" -e If server replies with errors, show them on stdout.\n" +" (no more than 1 error per second is displayed)\n" +" -q Quiet. Just show query/sec values\n" +" --precision Number of decimal places to display in latency output (default 0)\n" +" --csv Output in CSV format\n" +" -l Loop. Run the tests forever\n" +" -t Only run the comma separated list of tests. The test\n" +" names are the same as the ones produced as output.\n" +" -I Idle mode. Just open N idle connections and wait.\n\n" +"Examples:\n\n" +" Run the benchmark with the default configuration against 127.0.0.1:6379:\n" +" $ keydb-benchmark\n\n" +" Use 20 parallel clients, for a total of 100k requests, against 192.168.1.1:\n" +" $ keydb-benchmark -h 192.168.1.1 -p 6379 -n 100000 -c 20\n\n" +" Fill 127.0.0.1:6379 with about 1 million keys only using the SET test:\n" +" $ keydb-benchmark -t set -n 1000000 -r 100000000\n\n" +" Benchmark 127.0.0.1:6379 for a few commands producing CSV output:\n" +" $ keydb-benchmark -t ping,set,get -n 100000 --csv\n\n" +" Benchmark a specific command line:\n" +" $ keydb-benchmark -r 10000 -n 10000 eval 'return redis.call(\"ping\")' 0\n\n" +" Fill a list with 10000 random elements:\n" +" $ keydb-benchmark -r 10000 -n 10000 lpush mylist __rand_int__\n\n" +" On user specified command lines __rand_int__ is replaced with a random integer\n" +" with a range of values selected by the -r option.\n" + ); + exit(exit_status); +} + +int showThroughput(struct aeEventLoop *eventLoop, long long id, void *clientData) { + UNUSED(eventLoop); + UNUSED(id); + UNUSED(clientData); + int liveclients = 0; + int requests_finished = 0; + atomicGet(config.liveclients, liveclients); + atomicGet(config.requests_finished, requests_finished); + + if (liveclients == 0 && requests_finished != config.requests) { + fprintf(stderr,"All clients disconnected... aborting.\n"); + exit(1); + } + if (config.num_threads && requests_finished >= config.requests) { + aeStop(eventLoop); + return AE_NOMORE; + } + if (config.csv) return 250; + if (config.idlemode == 1) { + printf("clients: %d\r", config.liveclients); + fflush(stdout); + return 250; + } + float dt = (float)(mstime()-config.start)/1000.0; + float rps = (float)requests_finished/dt; + printf("%s: %.2f\r", config.title, rps); + fflush(stdout); + return 250; /* every 250ms */ +} + +/* Return true if the named test was selected using the -t command line + * switch, or if all the tests are selected (no -t passed by user). */ +int test_is_selected(const char *name) { + char buf[256]; + int l = strlen(name); + + if (config.tests == NULL) return 1; + buf[0] = ','; + memcpy(buf+1,name,l); + buf[l+1] = ','; + buf[l+2] = '\0'; + return strstr(config.tests,buf) != NULL; +} + +int main(int argc, const char **argv) { + int i; + char *data, *cmd; + const char *tag; + int len; + + client c; + + storage_init(NULL, 0); + + srandom(time(NULL)); + signal(SIGHUP, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + + config.numclients = 50; + config.requests = 100000; + config.liveclients = 0; + config.el = aeCreateEventLoop(1024*10); + aeCreateTimeEvent(config.el,1,showThroughput,NULL,NULL); + config.keepalive = 1; + config.datasize = 3; + config.pipeline = 1; + config.showerrors = 0; + config.randomkeys = 0; + config.randomkeys_keyspacelen = 0; + config.quiet = 0; + config.csv = 0; + config.loop = 0; + config.idlemode = 0; + config.latency = NULL; + config.clients = listCreate(); + config.hostip = "127.0.0.1"; + config.hostport = 6379; + config.hostsocket = NULL; + config.tests = NULL; + config.dbnum = 0; + config.auth = NULL; + config.precision = 1; + config.num_threads = 0; + config.threads = NULL; + config.cluster_mode = 0; + config.cluster_node_count = 0; + config.cluster_nodes = NULL; + config.redis_config = NULL; + config.is_fetching_slots = 0; + config.is_updating_slots = 0; + config.slots_last_update = 0; + config.enable_tracking = 0; + + i = parseOptions(argc,argv); + argc -= i; + argv += i; + + config.latency = (long long*)zmalloc(sizeof(long long)*config.requests, MALLOC_LOCAL); + + tag = ""; + + if (config.cluster_mode) { + // We only include the slot placeholder {tag} if cluster mode is enabled + tag = ":{tag}"; + + /* Fetch cluster configuration. */ + if (!fetchClusterConfiguration() || !config.cluster_nodes) { + if (!config.hostsocket) { + fprintf(stderr, "Failed to fetch cluster configuration from " + "%s:%d\n", config.hostip, config.hostport); + } else { + fprintf(stderr, "Failed to fetch cluster configuration from " + "%s\n", config.hostsocket); + } + exit(1); + } + if (config.cluster_node_count <= 1) { + fprintf(stderr, "Invalid cluster: %d node(s).\n", + config.cluster_node_count); + exit(1); + } + printf("Cluster has %d master nodes:\n\n", config.cluster_node_count); + int i = 0; + for (; i < config.cluster_node_count; i++) { + clusterNode *node = config.cluster_nodes[i]; + if (!node) { + fprintf(stderr, "Invalid cluster node #%d\n", i); + exit(1); + } + printf("Master %d: ", i); + if (node->name) printf("%s ", node->name); + printf("%s:%d\n", node->ip, node->port); + node->redis_config = getRedisConfig(node->ip, node->port, NULL); + if (node->redis_config == NULL) { + fprintf(stderr, "WARN: could not fetch node CONFIG %s:%d\n", + node->ip, node->port); + } + } + printf("\n"); + /* Automatically set thread number to node count if not specified + * by the user. */ + if (config.num_threads == 0) + config.num_threads = config.cluster_node_count; + } else { + config.redis_config = + getRedisConfig(config.hostip, config.hostport, config.hostsocket); + if (config.redis_config == NULL) + fprintf(stderr, "WARN: could not fetch server CONFIG\n"); + } + + if (config.num_threads > 0) { + int err = 0; + err |= pthread_mutex_init(&(config.requests_issued_mutex), NULL); + err |= pthread_mutex_init(&(config.requests_finished_mutex), NULL); + err |= pthread_mutex_init(&(config.liveclients_mutex), NULL); + err |= pthread_mutex_init(&(config.is_fetching_slots_mutex), NULL); + err |= pthread_mutex_init(&(config.is_updating_slots_mutex), NULL); + err |= pthread_mutex_init(&(config.updating_slots_mutex), NULL); + err |= pthread_mutex_init(&(config.slots_last_update_mutex), NULL); + if (err != 0) + { + perror("Failed to initialize mutex"); + exit(EXIT_FAILURE); + } + } + + if (config.keepalive == 0) { + printf("WARNING: keepalive disabled, you probably need 'echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse' for Linux and 'sudo sysctl -w net.inet.tcp.msl=1000' for Mac OS X in order to use a lot of clients/requests\n"); + } + + if (config.idlemode) { + printf("Creating %d idle connections and waiting forever (Ctrl+C when done)\n", config.numclients); + int thread_id = -1, use_threads = (config.num_threads > 0); + if (use_threads) { + thread_id = 0; + initBenchmarkThreads(); + } + c = createClient("",0,NULL,thread_id); /* will never receive a reply */ + createMissingClients(c); + if (use_threads) startBenchmarkThreads(); + else aeMain(config.el); + /* and will wait for every */ + } + + /* Run benchmark with command in the remainder of the arguments. */ + if (argc) { + sds title = sdsnew(argv[0]); + for (i = 1; i < argc; i++) { + title = sdscatlen(title, " ", 1); + title = sdscatlen(title, (char*)argv[i], strlen(argv[i])); + } + + do { + len = redisFormatCommandArgv(&cmd,argc,argv,NULL); + benchmark(title,cmd,len); + free(cmd); + } while(config.loop); + + if (config.redis_config != NULL) freeRedisConfig(config.redis_config); + return 0; + } + + /* Run default benchmark suite. */ + data = (char*)zmalloc(config.datasize+1, MALLOC_LOCAL); + do { + genBenchmarkRandomData(data, config.datasize); + data[config.datasize] = '\0'; + + if (test_is_selected("ping_inline") || test_is_selected("ping")) + benchmark("PING_INLINE","PING\r\n",6); + + if (test_is_selected("ping_mbulk") || test_is_selected("ping")) { + len = redisFormatCommand(&cmd,"PING"); + benchmark("PING_BULK",cmd,len); + free(cmd); + } + + if (test_is_selected("set")) { + len = redisFormatCommand(&cmd,"SET key%s:__rand_int__ %s",tag,data); + benchmark("SET",cmd,len); + free(cmd); + } + + if (test_is_selected("get")) { + len = redisFormatCommand(&cmd,"GET key%s:__rand_int__",tag); + benchmark("GET",cmd,len); + free(cmd); + } + + if (test_is_selected("incr")) { + len = redisFormatCommand(&cmd,"INCR counter%s:__rand_int__",tag); + benchmark("INCR",cmd,len); + free(cmd); + } + + if (test_is_selected("lpush")) { + len = redisFormatCommand(&cmd,"LPUSH mylist%s %s",tag,data); + benchmark("LPUSH",cmd,len); + free(cmd); + } + + if (test_is_selected("rpush")) { + len = redisFormatCommand(&cmd,"RPUSH mylist%s %s",tag,data); + benchmark("RPUSH",cmd,len); + free(cmd); + } + + if (test_is_selected("lpop")) { + len = redisFormatCommand(&cmd,"LPOP mylist%s",tag); + benchmark("LPOP",cmd,len); + free(cmd); + } + + if (test_is_selected("rpop")) { + len = redisFormatCommand(&cmd,"RPOP mylist%s",tag); + benchmark("RPOP",cmd,len); + free(cmd); + } + + if (test_is_selected("sadd")) { + len = redisFormatCommand(&cmd, + "SADD myset%s element:__rand_int__",tag); + benchmark("SADD",cmd,len); + free(cmd); + } + + if (test_is_selected("hset")) { + len = redisFormatCommand(&cmd, + "HSET myhash%s element:__rand_int__ %s",tag,data); + benchmark("HSET",cmd,len); + free(cmd); + } + + if (test_is_selected("spop")) { + len = redisFormatCommand(&cmd,"SPOP myset%s",tag); + benchmark("SPOP",cmd,len); + free(cmd); + } + + if (test_is_selected("zadd")) { + const char *score = "0"; + if (config.randomkeys) score = "__rand_int__"; + len = redisFormatCommand(&cmd, + "ZADD myzset%s %s element:__rand_int__",tag,score); + benchmark("ZADD",cmd,len); + free(cmd); + } + + if (test_is_selected("zpopmin")) { + len = redisFormatCommand(&cmd,"ZPOPMIN myzset%s",tag); + benchmark("ZPOPMIN",cmd,len); + free(cmd); + } + + if (test_is_selected("lrange") || + test_is_selected("lrange_100") || + test_is_selected("lrange_300") || + test_is_selected("lrange_500") || + test_is_selected("lrange_600")) + { + len = redisFormatCommand(&cmd,"LPUSH mylist%s %s",tag,data); + benchmark("LPUSH (needed to benchmark LRANGE)",cmd,len); + free(cmd); + } + + if (test_is_selected("lrange") || test_is_selected("lrange_100")) { + len = redisFormatCommand(&cmd,"LRANGE mylist%s 0 99",tag); + benchmark("LRANGE_100 (first 100 elements)",cmd,len); + free(cmd); + } + + if (test_is_selected("lrange") || test_is_selected("lrange_300")) { + len = redisFormatCommand(&cmd,"LRANGE mylist%s 0 299",tag); + benchmark("LRANGE_300 (first 300 elements)",cmd,len); + free(cmd); + } + + if (test_is_selected("lrange") || test_is_selected("lrange_500")) { + len = redisFormatCommand(&cmd,"LRANGE mylist%s 0 449",tag); + benchmark("LRANGE_500 (first 450 elements)",cmd,len); + free(cmd); + } + + if (test_is_selected("lrange") || test_is_selected("lrange_600")) { + len = redisFormatCommand(&cmd,"LRANGE mylist%s 0 599",tag); + benchmark("LRANGE_600 (first 600 elements)",cmd,len); + free(cmd); + } + + if (test_is_selected("mset")) { + const char *cmd_argv[21]; + cmd_argv[0] = "MSET"; + sds key_placeholder = sdscatprintf(sdsnew(""),"key%s:__rand_int__",tag); + for (i = 1; i < 21; i += 2) { + cmd_argv[i] = key_placeholder; + cmd_argv[i+1] = data; + } + len = redisFormatCommandArgv(&cmd,21,cmd_argv,NULL); + benchmark("MSET (10 keys)",cmd,len); + free(cmd); + sdsfree(key_placeholder); + } + + if (!config.csv) printf("\n"); + } while(config.loop); + + if (config.redis_config != NULL) freeRedisConfig(config.redis_config); + + return 0; +} From 57836424e7f8d125a95d7358f061ad06d93ee990 Mon Sep 17 00:00:00 2001 From: christianEQ Date: Tue, 27 Apr 2021 21:14:05 +0000 Subject: [PATCH 02/41] updated makefile vars to keydb names Former-commit-id: e6ab823473f7f215dcd61c3101b7c9ad310a0483 --- src/Makefile | 116 +++++++++++++++++++++++++-------------------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/src/Makefile b/src/Makefile index 11516e15c..40f1be7cf 100644 --- a/src/Makefile +++ b/src/Makefile @@ -6,8 +6,8 @@ # what is needed for Redis plus the standard CFLAGS and LDFLAGS passed. # However when building the dependencies (Jemalloc, Lua, Hiredis, ...) # CFLAGS and LDFLAGS are propagated to the dependencies, so to pass -# flags only to be used when compiling / linking Redis itself REDIS_CFLAGS -# and REDIS_LDFLAGS are used instead (this is the case of 'make gcov'). +# flags only to be used when compiling / linking Redis itself KEYDB_CFLAGS +# and KEYDB_LDFLAGS are used instead (this is the case of 'make gcov'). # # Dependencies are stored in the Makefile.dep file. To rebuild this file # Just use 'make dep', but this is only needed by developers. @@ -20,7 +20,7 @@ DEPENDENCY_TARGETS=hiredis linenoise lua NODEPS:=clean distclean # Default settings -STD=-std=c11 -pedantic -DREDIS_STATIC='' +STD=-std=c11 -pedantic -DKEYDB_STATIC='' CXX_STD=-std=c++14 -pedantic -fno-rtti -D__STDC_FORMAT_MACROS ifneq (,$(findstring clang,$(CC))) ifneq (,$(findstring FreeBSD,$(uname_S))) @@ -104,9 +104,9 @@ endif # Override default settings if possible -include .make-settings -FINAL_CFLAGS=$(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(REDIS_CFLAGS) -FINAL_CXXFLAGS=$(CXX_STD) $(WARN) $(OPT) $(DEBUG) $(CXXFLAGS) $(REDIS_CFLAGS) -FINAL_LDFLAGS=$(LDFLAGS) $(REDIS_LDFLAGS) $(DEBUG) +FINAL_CFLAGS=$(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(KEYDB_CFLAGS) +FINAL_CXXFLAGS=$(CXX_STD) $(WARN) $(OPT) $(DEBUG) $(CXXFLAGS) $(KEYDB_CFLAGS) +FINAL_LDFLAGS=$(LDFLAGS) $(KEYDB_LDFLAGS) $(DEBUG) FINAL_LIBS+=-lm DEBUG=-g -ggdb @@ -275,11 +275,11 @@ endif FINAL_LIBS += ../deps/hiredis/libhiredis_ssl.a $(LIBSSL_LIBS) $(LIBCRYPTO_LIBS) endif -REDIS_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS) -REDIS_CXX=$(QUIET_CC)$(CXX) $(FINAL_CXXFLAGS) +KEYDB_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS) +KEYDB_CXX=$(QUIET_CC)$(CXX) $(FINAL_CXXFLAGS) KEYDB_AS=$(QUIET_CC) as --64 -g -REDIS_LD=$(QUIET_LINK)$(CXX) $(FINAL_LDFLAGS) -REDIS_INSTALL=$(QUIET_INSTALL)$(INSTALL) +KEYDB_LD=$(QUIET_LINK)$(CXX) $(FINAL_LDFLAGS) +KEYDB_INSTALL=$(QUIET_INSTALL)$(INSTALL) CCCOLOR="\033[34m" LINKCOLOR="\033[34;1m" @@ -294,24 +294,24 @@ QUIET_LINK = @printf ' %b %b\n' $(LINKCOLOR)LINK$(ENDCOLOR) $(BINCOLOR)$@$(EN QUIET_INSTALL = @printf ' %b %b\n' $(LINKCOLOR)INSTALL$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR); endif -REDIS_SERVER_NAME=keydb-server$(PROG_SUFFIX) -REDIS_SENTINEL_NAME=keydb-sentinel$(PROG_SUFFIX) -REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o t_nhash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crcspeed.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o acl.o storage.o rdb-s3.o fastlock.o new.o tracking.o cron.o connection.o tls.o sha256.o motd.o timeout.o setcpuaffinity.o $(ASM_OBJ) -REDIS_CLI_NAME=keydb-cli$(PROG_SUFFIX) -REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o redis-cli-cpphelper.o zmalloc.o release.o anet.o ae.o crcspeed.o crc64.o siphash.o crc16.o storage-lite.o fastlock.o new.o motd.o $(ASM_OBJ) -REDIS_BENCHMARK_NAME=keydb-benchmark$(PROG_SUFFIX) -REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o siphash.o redis-benchmark.o storage-lite.o fastlock.o new.o $(ASM_OBJ) -REDIS_CHECK_RDB_NAME=keydb-check-rdb$(PROG_SUFFIX) -REDIS_CHECK_AOF_NAME=keydb-check-aof$(PROG_SUFFIX) -REDIS_DIAGNOSTIC_NAME=keydb-diagnostic-tool$(PROG_SUFFIX) +KEYDB_SERVER_NAME=keydb-server$(PROG_SUFFIX) +KEYDB_SENTINEL_NAME=keydb-sentinel$(PROG_SUFFIX) +KEYDB_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o t_nhash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crcspeed.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o acl.o storage.o rdb-s3.o fastlock.o new.o tracking.o cron.o connection.o tls.o sha256.o motd.o timeout.o setcpuaffinity.o $(ASM_OBJ) +KEYDB_CLI_NAME=keydb-cli$(PROG_SUFFIX) +KEYDB_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o redis-cli-cpphelper.o zmalloc.o release.o anet.o ae.o crcspeed.o crc64.o siphash.o crc16.o storage-lite.o fastlock.o new.o motd.o $(ASM_OBJ) +KEYDB_BENCHMARK_NAME=keydb-benchmark$(PROG_SUFFIX) +KEYDB_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o siphash.o redis-benchmark.o storage-lite.o fastlock.o new.o $(ASM_OBJ) +KEYDB_CHECK_RDB_NAME=keydb-check-rdb$(PROG_SUFFIX) +KEYDB_CHECK_AOF_NAME=keydb-check-aof$(PROG_SUFFIX) +KEYDB_DIAGNOSTIC_NAME=keydb-diagnostic-tool$(PROG_SUFFIX) -all: $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME) $(REDIS_DIAGNOSTIC_NAME) +all: $(KEYDB_SERVER_NAME) $(KEYDB_SENTINEL_NAME) $(KEYDB_CLI_NAME) $(KEYDB_BENCHMARK_NAME) $(KEYDB_CHECK_RDB_NAME) $(KEYDB_CHECK_AOF_NAME) $(KEYDB_DIAGNOSTIC_NAME) @echo "" @echo "Hint: It's a good idea to run 'make test' ;)" @echo "" Makefile.dep: - -$(REDIS_CC) -MM *.c > Makefile.dep 2> /dev/null || true + -$(KEYDB_CC) -MM *.c > Makefile.dep 2> /dev/null || true ifeq (0, $(words $(findstring $(MAKECMDGOALS), $(NODEPS)))) -include Makefile.dep @@ -329,9 +329,9 @@ persist-settings: distclean echo CFLAGS=$(CFLAGS) >> .make-settings echo CXXFLAGS=$(CXXFLAGS) >> .make-settings echo LDFLAGS=$(LDFLAGS) >> .make-settings - echo REDIS_CFLAGS=$(REDIS_CFLAGS) >> .make-settings - echo REDIS_CXXFLAGS=$(REDIS_CXXFLAGS) >> .make-settings - echo REDIS_LDFLAGS=$(REDIS_LDFLAGS) >> .make-settings + echo KEYDB_CFLAGS=$(KEYDB_CFLAGS) >> .make-settings + echo KEYDB_CXXFLAGS=$(KEYDB_CXXFLAGS) >> .make-settings + echo KEYDB_LDFLAGS=$(KEYDB_LDFLAGS) >> .make-settings echo PREV_FINAL_CFLAGS=$(FINAL_CFLAGS) >> .make-settings echo PREV_FINAL_CXXFLAGS=$(FINAL_CXXFLAGS) >> .make-settings echo PREV_FINAL_LDFLAGS=$(FINAL_LDFLAGS) >> .make-settings @@ -354,53 +354,53 @@ endif @touch $@ # keydb-server -$(REDIS_SERVER_NAME): $(REDIS_SERVER_OBJ) - $(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/lua/src/liblua.a $(FINAL_LIBS) +$(KEYDB_SERVER_NAME): $(KEYDB_SERVER_OBJ) + $(KEYDB_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/lua/src/liblua.a $(FINAL_LIBS) # keydb-sentinel -$(REDIS_SENTINEL_NAME): $(REDIS_SERVER_NAME) - $(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) +$(KEYDB_SENTINEL_NAME): $(KEYDB_SERVER_NAME) + $(KEYDB_INSTALL) $(KEYDB_SERVER_NAME) $(KEYDB_SENTINEL_NAME) # keydb-check-rdb -$(REDIS_CHECK_RDB_NAME): $(REDIS_SERVER_NAME) - $(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(REDIS_CHECK_RDB_NAME) +$(KEYDB_CHECK_RDB_NAME): $(KEYDB_SERVER_NAME) + $(KEYDB_INSTALL) $(KEYDB_SERVER_NAME) $(KEYDB_CHECK_RDB_NAME) # keydb-check-aof -$(REDIS_CHECK_AOF_NAME): $(REDIS_SERVER_NAME) - $(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(REDIS_CHECK_AOF_NAME) +$(KEYDB_CHECK_AOF_NAME): $(KEYDB_SERVER_NAME) + $(KEYDB_INSTALL) $(KEYDB_SERVER_NAME) $(KEYDB_CHECK_AOF_NAME) # keydb-cli -$(REDIS_CLI_NAME): $(REDIS_CLI_OBJ) - $(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o $(FINAL_LIBS) +$(KEYDB_CLI_NAME): $(KEYDB_CLI_OBJ) + $(KEYDB_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/linenoise/linenoise.o $(FINAL_LIBS) # keydb-benchmark -$(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ) - $(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a $(FINAL_LIBS) +$(KEYDB_BENCHMARK_NAME): $(KEYDB_BENCHMARK_OBJ) + $(KEYDB_LD) -o $@ $^ ../deps/hiredis/libhiredis.a $(FINAL_LIBS) # keydb-diagnostic-tool -$(REDIS_DIAGNOSTIC_NAME): $(REDIS_BENCHMARK_OBJ) - $(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a $(FINAL_LIBS) +$(KEYDB_DIAGNOSTIC_NAME): $(KEYDB_BENCHMARK_OBJ) + $(KEYDB_LD) -o $@ $^ ../deps/hiredis/libhiredis.a $(FINAL_LIBS) dict-benchmark: dict.cpp zmalloc.cpp sds.c siphash.c - $(REDIS_CC) $(FINAL_CFLAGS) $^ -D DICT_BENCHMARK_MAIN -o $@ $(FINAL_LIBS) + $(KEYDB_CC) $(FINAL_CFLAGS) $^ -D DICT_BENCHMARK_MAIN -o $@ $(FINAL_LIBS) -DEP = $(REDIS_SERVER_OBJ:%.o=%.d) $(REDIS_CLI_OBJ:%.o=%.d) $(REDIS_BENCHMARK_OBJ:%.o=%.d) +DEP = $(KEYDB_SERVER_OBJ:%.o=%.d) $(KEYDB_CLI_OBJ:%.o=%.d) $(KEYDB_BENCHMARK_OBJ:%.o=%.d) -include $(DEP) # Because the jemalloc.h header is generated as a part of the jemalloc build, # building it should complete before building any other object. Instead of # depending on a single artifact, build all dependencies first. %.o: %.c .make-prerequisites - $(REDIS_CC) -MMD -o $@ -c $< + $(KEYDB_CC) -MMD -o $@ -c $< %.o: %.cpp .make-prerequisites - $(REDIS_CXX) -MMD -o $@ -c $< + $(KEYDB_CXX) -MMD -o $@ -c $< %.o: %.asm .make-prerequisites $(KEYDB_AS) $< -o $@ clean: - rm -rf $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME) $(REDIS_DIAGNOSTIC_NAME) *.o *.gcda *.gcno *.gcov KeyDB.info lcov-html Makefile.dep dict-benchmark + rm -rf $(KEYDB_SERVER_NAME) $(KEYDB_SENTINEL_NAME) $(KEYDB_CLI_NAME) $(KEYDB_BENCHMARK_NAME) $(KEYDB_CHECK_RDB_NAME) $(KEYDB_CHECK_AOF_NAME) $(KEYDB_DIAGNOSTIC_NAME) *.o *.gcda *.gcno *.gcov KeyDB.info lcov-html Makefile.dep dict-benchmark rm -f $(DEP) .PHONY: clean @@ -412,10 +412,10 @@ distclean: clean .PHONY: distclean -test: $(REDIS_SERVER_NAME) $(REDIS_CHECK_AOF_NAME) +test: $(KEYDB_SERVER_NAME) $(KEYDB_CHECK_AOF_NAME) @(cd ..; ./runtest) -test-sentinel: $(REDIS_SENTINEL_NAME) +test-sentinel: $(KEYDB_SENTINEL_NAME) @(cd ..; ./runtest-sentinel) check: test @@ -428,13 +428,13 @@ lcov: @genhtml --legend -o lcov-html KeyDB.info | grep lines | awk '{print $$2;}' | sed 's/%//g' test-sds: sds.c sds.h - $(REDIS_CC) sds.c zmalloc.cpp -DSDS_TEST_MAIN $(FINAL_LIBS) -o /tmp/sds_test + $(KEYDB_CC) sds.c zmalloc.cpp -DSDS_TEST_MAIN $(FINAL_LIBS) -o /tmp/sds_test /tmp/sds_test .PHONY: lcov -bench: $(REDIS_BENCHMARK_NAME) - ./$(REDIS_BENCHMARK_NAME) +bench: $(KEYDB_BENCHMARK_NAME) + ./$(KEYDB_BENCHMARK_NAME) 32bit: @echo "" @@ -443,7 +443,7 @@ bench: $(REDIS_BENCHMARK_NAME) $(MAKE) CXXFLAGS="-m32" CFLAGS="-m32" LDFLAGS="-m32" gcov: - $(MAKE) REDIS_CXXFLAGS="-fprofile-arcs -ftest-coverage -DCOVERAGE_TEST" REDIS_CFLAGS="-fprofile-arcs -ftest-coverage -DCOVERAGE_TEST" REDIS_LDFLAGS="-fprofile-arcs -ftest-coverage" + $(MAKE) KEYDB_CXXFLAGS="-fprofile-arcs -ftest-coverage -DCOVERAGE_TEST" KEYDB_CFLAGS="-fprofile-arcs -ftest-coverage -DCOVERAGE_TEST" KEYDB_LDFLAGS="-fprofile-arcs -ftest-coverage" noopt: $(MAKE) OPTIMIZATION="-O0" @@ -459,13 +459,13 @@ src/help.h: install: all @mkdir -p $(INSTALL_BIN) - $(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(INSTALL_BIN) - $(REDIS_INSTALL) $(REDIS_BENCHMARK_NAME) $(INSTALL_BIN) - $(REDIS_INSTALL) $(REDIS_CLI_NAME) $(INSTALL_BIN) - $(REDIS_INSTALL) $(REDIS_CHECK_RDB_NAME) $(INSTALL_BIN) - $(REDIS_INSTALL) $(REDIS_CHECK_AOF_NAME) $(INSTALL_BIN) - $(REDIS_INSTALL) $(REDIS_DIAGNOSTIC_NAME) $(INSTALL_BIN) - @ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_SENTINEL_NAME) + $(KEYDB_INSTALL) $(KEYDB_SERVER_NAME) $(INSTALL_BIN) + $(KEYDB_INSTALL) $(KEYDB_BENCHMARK_NAME) $(INSTALL_BIN) + $(KEYDB_INSTALL) $(KEYDB_CLI_NAME) $(INSTALL_BIN) + $(KEYDB_INSTALL) $(KEYDB_CHECK_RDB_NAME) $(INSTALL_BIN) + $(KEYDB_INSTALL) $(KEYDB_CHECK_AOF_NAME) $(INSTALL_BIN) + $(KEYDB_INSTALL) $(KEYDB_DIAGNOSTIC_NAME) $(INSTALL_BIN) + @ln -sf $(KEYDB_SERVER_NAME) $(INSTALL_BIN)/$(KEYDB_SENTINEL_NAME) uninstall: - rm -f $(INSTALL_BIN)/{$(REDIS_SERVER_NAME),$(REDIS_BENCHMARK_NAME),$(REDIS_CLI_NAME),$(REDIS_CHECK_RDB_NAME),$(REDIS_CHECK_AOF_NAME),$(REDIS_SENTINEL_NAME),$(REDIS_DIAGNOSTIC_NAME)} + rm -f $(INSTALL_BIN)/{$(KEYDB_SERVER_NAME),$(KEYDB_BENCHMARK_NAME),$(KEYDB_CLI_NAME),$(KEYDB_CHECK_RDB_NAME),$(KEYDB_CHECK_AOF_NAME),$(KEYDB_SENTINEL_NAME),$(KEYDB_DIAGNOSTIC_NAME)} From 060b9192505d78a16d6da4ae1bf6d1b576617632 Mon Sep 17 00:00:00 2001 From: christianEQ Date: Wed, 28 Apr 2021 16:02:44 +0000 Subject: [PATCH 03/41] fixed diagnostic tool to use correct obj files Former-commit-id: 66547bd28ab025c4d118e6b8d35e2aa0b1f42f10 --- src/Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index 40f1be7cf..5781c2a7d 100644 --- a/src/Makefile +++ b/src/Makefile @@ -304,6 +304,7 @@ KEYDB_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o siph KEYDB_CHECK_RDB_NAME=keydb-check-rdb$(PROG_SUFFIX) KEYDB_CHECK_AOF_NAME=keydb-check-aof$(PROG_SUFFIX) KEYDB_DIAGNOSTIC_NAME=keydb-diagnostic-tool$(PROG_SUFFIX) +KEYDB_DIAGNOSTIC_OBJ=ae.o anet.o keydb-diagnostic-tool.o adlist.o dict.o zmalloc.o siphash.o keydb-diagnostic-tool.o storage-lite.o fastlock.o new.o $(ASM_OBJ) all: $(KEYDB_SERVER_NAME) $(KEYDB_SENTINEL_NAME) $(KEYDB_CLI_NAME) $(KEYDB_BENCHMARK_NAME) $(KEYDB_CHECK_RDB_NAME) $(KEYDB_CHECK_AOF_NAME) $(KEYDB_DIAGNOSTIC_NAME) @echo "" @@ -378,7 +379,7 @@ $(KEYDB_BENCHMARK_NAME): $(KEYDB_BENCHMARK_OBJ) $(KEYDB_LD) -o $@ $^ ../deps/hiredis/libhiredis.a $(FINAL_LIBS) # keydb-diagnostic-tool -$(KEYDB_DIAGNOSTIC_NAME): $(KEYDB_BENCHMARK_OBJ) +$(KEYDB_DIAGNOSTIC_NAME): $(KEYDB_DIAGNOSTIC_OBJ) $(KEYDB_LD) -o $@ $^ ../deps/hiredis/libhiredis.a $(FINAL_LIBS) dict-benchmark: dict.cpp zmalloc.cpp sds.c siphash.c From d01afe885c06c194343409dc731ac9ec1fa8046b Mon Sep 17 00:00:00 2001 From: Madelyn Olson <34459052+madolson@users.noreply.github.com> Date: Mon, 19 Apr 2021 22:16:27 -0700 Subject: [PATCH 04/41] Fix memory leak when doing lazyfreeing client tracking table (#8822) Interior rax pointers were not being freed (cherry picked from commit c73b4ddfd96d00ed0d0fde17953ce63d78bc3777) --- src/lazyfree.c | 5 ++--- src/server.h | 1 + tests/unit/tracking.tcl | 11 +++++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/lazyfree.c b/src/lazyfree.c index f18b2027f..a2cf2c3ed 100644 --- a/src/lazyfree.c +++ b/src/lazyfree.c @@ -39,12 +39,11 @@ void lazyfreeFreeSlotsMap(void *args[]) { atomicIncr(lazyfreed_objects,len); } -/* Release the rax mapping Redis Cluster keys to slots in the - * lazyfree thread. */ +/* Release the key tracking table. */ void lazyFreeTrackingTable(void *args[]) { rax *rt = args[0]; size_t len = rt->numele; - raxFree(rt); + freeTrackingRadixTree(rt); atomicDecr(lazyfree_objects,len); atomicIncr(lazyfreed_objects,len); } diff --git a/src/server.h b/src/server.h index d35eaa425..a2a722c6d 100644 --- a/src/server.h +++ b/src/server.h @@ -1911,6 +1911,7 @@ void disableTracking(client *c); void trackingRememberKeys(client *c); void trackingInvalidateKey(client *c, robj *keyobj); void trackingInvalidateKeysOnFlush(int async); +void freeTrackingRadixTree(rax *rt); void freeTrackingRadixTreeAsync(rax *rt); void trackingLimitUsedSlots(void); uint64_t trackingGetTotalItems(void); diff --git a/tests/unit/tracking.tcl b/tests/unit/tracking.tcl index 40f1a2a66..4c75b6f48 100644 --- a/tests/unit/tracking.tcl +++ b/tests/unit/tracking.tcl @@ -395,6 +395,17 @@ start_server {tags {"tracking network"}} { assert {[lindex msg 2] eq {} } } + test {Test ASYNC flushall} { + clean_all + r CLIENT TRACKING on REDIRECT $redir_id + r GET key1 + r GET key2 + assert_equal [s 0 tracking_total_keys] 2 + $rd_sg FLUSHALL ASYNC + assert_equal [s 0 tracking_total_keys] 0 + assert_equal [lindex [$rd_redirection read] 2] {} + } + # Keys are defined to be evicted 100 at a time by default. # If after eviction the number of keys still surpasses the limit # defined in tracking-table-max-keys, we increases eviction From b97a4ad7f2ab50f77993351e61c197eb55347873 Mon Sep 17 00:00:00 2001 From: Huang Zhw Date: Tue, 20 Apr 2021 15:59:44 +0800 Subject: [PATCH 05/41] Fix migrateCommand may migrate wrong value. (#8815) This scene is hard to happen. When first attempt some keys expired, only kv position is updated not ov. Then socket err happens, second attempt is taken. This time kv items may be mismatching with ov items. (cherry picked from commit 080d4579db40d965f8392af5b1da7a99d1a817d5) --- src/cluster.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cluster.c b/src/cluster.c index 0f0ab737e..ba21024be 100644 --- a/src/cluster.c +++ b/src/cluster.c @@ -5465,9 +5465,10 @@ try_again: if (ttl < 1) ttl = 1; } - /* Relocate valid (non expired) keys into the array in successive + /* Relocate valid (non expired) keys and values into the array in successive * positions to remove holes created by the keys that were present * in the first lookup but are now expired after the second lookup. */ + ov[non_expired] = ov[j]; kv[non_expired++] = kv[j]; serverAssertWithInfo(c,NULL, From 0851705304f5277c9adac653b423d634ebc5101c Mon Sep 17 00:00:00 2001 From: bugwz Date: Wed, 21 Apr 2021 02:51:24 +0800 Subject: [PATCH 06/41] Print the number of abnormal line in AOF (#8823) When redis-check-aof finds an error, it prints the line number for faster troubleshooting. (cherry picked from commit 761d7d27711edfbf737def41ff28f5b325fb16c8) --- src/redis-check-aof.c | 6 ++++-- tests/integration/aof.tcl | 12 ++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/redis-check-aof.c b/src/redis-check-aof.c index eedb09db5..1507e0a06 100644 --- a/src/redis-check-aof.c +++ b/src/redis-check-aof.c @@ -39,12 +39,14 @@ static char error[1044]; static off_t epos; +static long long line = 1; int consumeNewline(char *buf) { if (strncmp(buf,"\r\n",2) != 0) { ERROR("Expected \\r\\n, got: %02x%02x",buf[0],buf[1]); return 0; } + line += 1; return 1; } @@ -201,8 +203,8 @@ int redis_check_aof_main(int argc, char **argv) { off_t pos = process(fp); off_t diff = size-pos; - printf("AOF analyzed: size=%lld, ok_up_to=%lld, diff=%lld\n", - (long long) size, (long long) pos, (long long) diff); + printf("AOF analyzed: size=%lld, ok_up_to=%lld, ok_up_to_line=%lld, diff=%lld\n", + (long long) size, (long long) pos, line, (long long) diff); if (diff > 0) { if (fix) { char buf[2]; diff --git a/tests/integration/aof.tcl b/tests/integration/aof.tcl index e64e2022a..abe2dc10c 100644 --- a/tests/integration/aof.tcl +++ b/tests/integration/aof.tcl @@ -158,6 +158,18 @@ tags {"aof"} { assert_match "*not valid*" $result } + test "Short read: Utility should show the abnormal line num in AOF" { + create_aof { + append_to_aof [formatCommand set foo hello] + append_to_aof "!!!" + } + + catch { + exec src/redis-check-aof $aof_path + } result + assert_match "*ok_up_to_line=8*" $result + } + test "Short read: Utility should be able to fix the AOF" { set result [exec src/redis-check-aof --fix $aof_path << "y\n"] assert_match "*Successfully truncated AOF*" $result From ef64333e6390653e6bbdedf18c60cc3449bbd5a1 Mon Sep 17 00:00:00 2001 From: Wang Yuan Date: Thu, 22 Apr 2021 13:32:43 +0800 Subject: [PATCH 07/41] Expire key firstly and then notify keyspace event (#8830) Modules event subscribers may get wrong things in notifyKeyspaceEvent callback, such as wrong number of keys, or be able to lookup this key. This commit changes the order to be like the one in evict.c. Cleanup: Since we know the key exists (it expires now), db*Delete is sure to return 1, so there's no need to check it's output (misleading). (cherry picked from commit 63acfe4b00b9d3e34a53559f965c0bc44c03db61) --- src/db.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/db.c b/src/db.c index ec68c228c..40377ec3f 100644 --- a/src/db.c +++ b/src/db.c @@ -1541,14 +1541,17 @@ int expireIfNeeded(redisDb *db, robj *key) { if (checkClientPauseTimeoutAndReturnIfPaused()) return 1; /* Delete the key */ + if (server.lazyfree_lazy_expire) { + dbAsyncDelete(db,key); + } else { + dbSyncDelete(db,key); + } server.stat_expiredkeys++; propagateExpire(db,key,server.lazyfree_lazy_expire); notifyKeyspaceEvent(NOTIFY_EXPIRED, "expired",key,db->id); - int retval = server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) : - dbSyncDelete(db,key); - if (retval) signalModifiedKey(NULL,db,key); - return retval; + signalModifiedKey(NULL,db,key); + return 1; } /* ----------------------------------------------------------------------------- From 6384fe341472598eee92439fc16b088706fa54f3 Mon Sep 17 00:00:00 2001 From: zyxwvu Shi Date: Thu, 22 Apr 2021 13:59:10 +0800 Subject: [PATCH 08/41] Use monotonic clock to check for Lua script timeout. (#8812) This prevents a case where NTP moves the system clock forward resulting in a false detection of a busy script. Signed-off-by: zyxwvu Shi (cherry picked from commit f61c37cec900ba391541f20f7655aad44a26bafc) --- src/db.c | 2 +- src/scripting.c | 11 +++++++---- src/server.h | 3 ++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/db.c b/src/db.c index 40377ec3f..840e95e21 100644 --- a/src/db.c +++ b/src/db.c @@ -1480,7 +1480,7 @@ int keyIsExpired(redisDb *db, robj *key) { * script execution, making propagation to slaves / AOF consistent. * See issue #1525 on Github for more information. */ if (server.lua_caller) { - now = server.lua_time_start; + now = server.lua_time_snapshot; } /* If we are in the middle of a command execution, we still want to use * a reference time that does not change: in that case we just use the diff --git a/src/scripting.c b/src/scripting.c index 299e60810..dbbd50eaf 100644 --- a/src/scripting.c +++ b/src/scripting.c @@ -31,6 +31,7 @@ #include "sha1.h" #include "rand.h" #include "cluster.h" +#include "monotonic.h" #include #include @@ -1427,7 +1428,7 @@ sds luaCreateFunction(client *c, lua_State *lua, robj *body) { /* This is the Lua script "count" hook that we use to detect scripts timeout. */ void luaMaskCountHook(lua_State *lua, lua_Debug *ar) { - long long elapsed = mstime() - server.lua_time_start; + long long elapsed = elapsedMs(server.lua_time_start); UNUSED(ar); UNUSED(lua); @@ -1578,7 +1579,8 @@ void evalGenericCommand(client *c, int evalsha) { server.in_eval = 1; server.lua_caller = c; server.lua_cur_script = funcname + 2; - server.lua_time_start = mstime(); + server.lua_time_start = getMonotonicUs(); + server.lua_time_snapshot = mstime(); server.lua_kill = 0; if (server.lua_time_limit > 0 && ldb.active == 0) { lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000); @@ -2729,7 +2731,7 @@ void luaLdbLineHook(lua_State *lua, lua_Debug *ar) { /* Check if a timeout occurred. */ if (ar->event == LUA_HOOKCOUNT && ldb.step == 0 && bp == 0) { - mstime_t elapsed = mstime() - server.lua_time_start; + mstime_t elapsed = elapsedMs(server.lua_time_start); mstime_t timelimit = server.lua_time_limit ? server.lua_time_limit : 5000; if (elapsed >= timelimit) { @@ -2759,6 +2761,7 @@ void luaLdbLineHook(lua_State *lua, lua_Debug *ar) { lua_pushstring(lua, "timeout during Lua debugging with client closing connection"); lua_error(lua); } - server.lua_time_start = mstime(); + server.lua_time_start = getMonotonicUs(); + server.lua_time_snapshot = mstime(); } } diff --git a/src/server.h b/src/server.h index a2a722c6d..9b7e16a0d 100644 --- a/src/server.h +++ b/src/server.h @@ -1571,7 +1571,8 @@ struct redisServer { dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */ unsigned long long lua_scripts_mem; /* Cached scripts' memory + oh */ mstime_t lua_time_limit; /* Script timeout in milliseconds */ - mstime_t lua_time_start; /* Start time of script, milliseconds time */ + monotime lua_time_start; /* monotonic timer to detect timed-out script */ + mstime_t lua_time_snapshot; /* Snapshot of mstime when script is started */ int lua_write_dirty; /* True if a write command was called during the execution of the current script. */ int lua_random_dirty; /* True if a random command was called during the From 2ccb9263140a383f1916e8df522e14f36aed76da Mon Sep 17 00:00:00 2001 From: Istemi Ekin Akkus <5419814+iakkus@users.noreply.github.com> Date: Sun, 25 Apr 2021 09:05:12 +0200 Subject: [PATCH 09/41] Modules: Fix RM_GetClusterNodeInfo() to correctly populate the master_id (#8846) (cherry picked from commit af035c1e1d3bcaf662051cff4dc49f6051321c9c) --- src/module.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/module.c b/src/module.c index 05bf3a275..727cdc43f 100644 --- a/src/module.c +++ b/src/module.c @@ -6168,7 +6168,7 @@ int RM_GetClusterNodeInfo(RedisModuleCtx *ctx, const char *id, char *ip, char *m /* If the information is not available, the function will set the * field to zero bytes, so that when the field can't be populated the * function kinda remains predictable. */ - if (node->flags & CLUSTER_NODE_MASTER && node->slaveof) + if (node->flags & CLUSTER_NODE_SLAVE && node->slaveof) memcpy(master_id,node->slaveof->name,REDISMODULE_NODE_ID_LEN); else memset(master_id,0,REDISMODULE_NODE_ID_LEN); From 16c53085f761d7b87de2021fc591e19ea7d899fd Mon Sep 17 00:00:00 2001 From: Yang Bodong Date: Sun, 25 Apr 2021 19:00:35 +0800 Subject: [PATCH 10/41] When the password is wrong, redis-benchmark should exit (#8855) (cherry picked from commit 8423b77f14c0d3a58e580c65a70b4f980f5cdcf6) --- src/redis-benchmark.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/redis-benchmark.c b/src/redis-benchmark.c index 351335862..068c094e1 100644 --- a/src/redis-benchmark.c +++ b/src/redis-benchmark.c @@ -1782,8 +1782,10 @@ int main(int argc, const char **argv) { } else { config.redis_config = getRedisConfig(config.hostip, config.hostport, config.hostsocket); - if (config.redis_config == NULL) + if (config.redis_config == NULL) { fprintf(stderr, "WARN: could not fetch server CONFIG\n"); + exit(1); + } } if (config.num_threads > 0) { pthread_mutex_init(&(config.liveclients_mutex), NULL); From 8cfa37fc21d5dfacbafc37b56443512a60925104 Mon Sep 17 00:00:00 2001 From: Yossi Gottlieb Date: Mon, 26 Apr 2021 18:43:57 +0300 Subject: [PATCH 11/41] Remove redundant -latomic on arm64. (#8867) (cherry picked from commit ebfbb091096b6f36cf82e9f6e6583b10fd5b5acb) --- src/Makefile | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Makefile b/src/Makefile index 28d50da02..62e37cb48 100644 --- a/src/Makefile +++ b/src/Makefile @@ -93,14 +93,10 @@ FINAL_LDFLAGS=$(LDFLAGS) $(REDIS_LDFLAGS) $(DEBUG) FINAL_LIBS=-lm DEBUG=-g -ggdb -# Linux ARM needs -latomic at linking time -ifneq (,$(filter aarch64 armv,$(uname_M))) - FINAL_LIBS+=-latomic -else +# Linux ARM32 needs -latomic at linking time ifneq (,$(findstring armv,$(uname_M))) FINAL_LIBS+=-latomic endif -endif ifeq ($(uname_S),SunOS) # SunOS From 6cbea7d29b5285692843bc1c351abba1a7ef326f Mon Sep 17 00:00:00 2001 From: Oran Agra Date: Tue, 27 Apr 2021 08:15:10 +0300 Subject: [PATCH 12/41] Prevent replicas from sending commands that interact with keyspace (#8868) This solves an issue reported in #8712 in which a replica would bypass the client write pause check and cause an assertion due to executing a write command during failover. The fact is that we don't expect replicas to execute any command other than maybe REPLCONF and PING, etc. but matching against the ADMIN command flag is insufficient, so instead i just block keyspace access for now. (cherry picked from commit 46f4ebbe842620f0976a36741a72482620aa4b48) --- src/server.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/server.c b/src/server.c index 993260619..1fe150b3b 100644 --- a/src/server.c +++ b/src/server.c @@ -3985,6 +3985,8 @@ int processCommand(client *c) { return C_OK; } + int is_read_command = (c->cmd->flags & CMD_READONLY) || + (c->cmd->proc == execCommand && (c->mstate.cmd_flags & CMD_READONLY)); int is_write_command = (c->cmd->flags & CMD_WRITE) || (c->cmd->proc == execCommand && (c->mstate.cmd_flags & CMD_WRITE)); int is_denyoom_command = (c->cmd->flags & CMD_DENYOOM) || @@ -4194,7 +4196,7 @@ int processCommand(client *c) { c->cmd->proc != discardCommand && c->cmd->proc != watchCommand && c->cmd->proc != unwatchCommand && - c->cmd->proc != resetCommand && + c->cmd->proc != resetCommand && !(c->cmd->proc == shutdownCommand && c->argc == 2 && tolower(((char*)c->argv[1]->ptr)[0]) == 'n') && @@ -4206,6 +4208,14 @@ int processCommand(client *c) { return C_OK; } + /* Prevent a replica from sending commands that access the keyspace. + * The main objective here is to prevent abuse of client pause check + * from which replicas are exempt. */ + if ((c->flags & CLIENT_SLAVE) && (is_may_replicate_command || is_write_command || is_read_command)) { + rejectCommandFormat(c, "Replica can't interract with the keyspace"); + return C_OK; + } + /* If the server is paused, block the client until * the pause has ended. Replicas are never paused. */ if (!(c->flags & CLIENT_SLAVE) && From 5c7b869e616fcb02939777e1056b03975c28af0f Mon Sep 17 00:00:00 2001 From: yoav-steinberg Date: Tue, 27 Apr 2021 16:22:22 +0300 Subject: [PATCH 13/41] Bump freebsd-vm version to fix CI failures (#8876) Specifically we had issues with NTP sync failure which was resolved here: https://github.com/vmactions/freebsd-vm/commit/457af7345642e154a79d219971a2d4a7c7fe2118 (cherry picked from commit 2e88b0639689a3019e27f55dfa40578847443eeb) --- .github/workflows/daily.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/daily.yml b/.github/workflows/daily.yml index ee9ac1bbf..9e4630e29 100644 --- a/.github/workflows/daily.yml +++ b/.github/workflows/daily.yml @@ -253,7 +253,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: test - uses: vmactions/freebsd-vm@v0.1.2 + uses: vmactions/freebsd-vm@v0.1.4 with: usesh: true sync: rsync From 34b9a3fa2ea40e7e6cfd39ebaea932e202d64fd1 Mon Sep 17 00:00:00 2001 From: Huang Zhw Date: Tue, 27 Apr 2021 23:02:23 +0800 Subject: [PATCH 14/41] Fix potential CONFIG SET bind test failure. (#8875) Use an invalid IP address to trigger CONFIG SET bind failure, instead of DNS which is not guaranteed to always fail. (cherry picked from commit 2b22fffc787e91df789dabf23ddcf19ecf34cf6f) --- tests/unit/networking.tcl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/networking.tcl b/tests/unit/networking.tcl index 19feee8c3..38b49d45e 100644 --- a/tests/unit/networking.tcl +++ b/tests/unit/networking.tcl @@ -25,7 +25,7 @@ test {CONFIG SET port number} { test {CONFIG SET bind address} { start_server {} { # non-valid address - catch {r CONFIG SET bind "some.wrong.bind.address"} e + catch {r CONFIG SET bind "999.999.999.999"} e assert_match {*Failed to bind to specified addresses*} $e # make sure server still bound to the previous address @@ -33,4 +33,4 @@ test {CONFIG SET bind address} { $rd PING $rd close } -} \ No newline at end of file +} From 4f2da00e94aed661942af3ea44782fc61f478e85 Mon Sep 17 00:00:00 2001 From: filipe oliveira Date: Wed, 28 Apr 2021 07:51:07 +0100 Subject: [PATCH 15/41] redis-benchmark: Error/Warning handling updates. (#8869) - Immediately exit on errors that are not related to topology updates. - Deprecates the `-e` option ( retro compatible ) and warns that we now exit immediately on errors that are not related to topology updates. - Fixed wrongfully failing on config fetch error (warning only). This only affects RE. Bottom line: - MOVED and ASK errors will not show any warning (unlike the throttled error with `-e` before). - CLUSTERDOWN still prints an error unconditionally and sleeps for 1 second. - other errors are fatal. (cherry picked from commit ef6f902372d4646b1894ec5dbd5f857dea5688d6) --- src/redis-benchmark.c | 82 +++++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/src/redis-benchmark.c b/src/redis-benchmark.c index 068c094e1..51dba9511 100644 --- a/src/redis-benchmark.c +++ b/src/redis-benchmark.c @@ -99,7 +99,6 @@ static struct config { int randomkeys_keyspacelen; int keepalive; int pipeline; - int showerrors; long long start; long long totlatency; const char *title; @@ -307,7 +306,9 @@ static redisContext *getRedisContext(const char *ip, int port, fprintf(stderr, "Node %s:%d replied with error:\n%s\n", ip, port, reply->str); else fprintf(stderr, "Node %s replied with error:\n%s\n", hostsocket, reply->str); - goto cleanup; + freeReplyObject(reply); + redisFree(ctx); + exit(1); } freeReplyObject(reply); return ctx; @@ -366,9 +367,15 @@ fail: fprintf(stderr, "ERROR: failed to fetch CONFIG from "); if (hostsocket == NULL) fprintf(stderr, "%s:%d\n", ip, port); else fprintf(stderr, "%s\n", hostsocket); + int abort_test = 0; + if (!strncmp(reply->str,"NOAUTH",5) || + !strncmp(reply->str,"WRONGPASS",9) || + !strncmp(reply->str,"NOPERM",5)) + abort_test = 1; freeReplyObject(reply); redisFree(c); freeRedisConfig(cfg); + if (abort_test) exit(1); return NULL; } static void freeRedisConfig(redisConfig *cfg) { @@ -513,44 +520,39 @@ static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) { exit(1); } redisReply *r = reply; - int is_err = (r->type == REDIS_REPLY_ERROR); - - if (is_err && config.showerrors) { - /* TODO: static lasterr_time not thread-safe */ - static time_t lasterr_time = 0; - time_t now = time(NULL); - if (lasterr_time != now) { - lasterr_time = now; - if (c->cluster_node) { - printf("Error from server %s:%d: %s\n", + if (r->type == REDIS_REPLY_ERROR) { + /* Try to update slots configuration if reply error is + * MOVED/ASK/CLUSTERDOWN and the key(s) used by the command + * contain(s) the slot hash tag. + * If the error is not topology-update related then we + * immediately exit to avoid false results. */ + if (c->cluster_node && c->staglen) { + int fetch_slots = 0, do_wait = 0; + if (!strncmp(r->str,"MOVED",5) || !strncmp(r->str,"ASK",3)) + fetch_slots = 1; + else if (!strncmp(r->str,"CLUSTERDOWN",11)) { + /* Usually the cluster is able to recover itself after + * a CLUSTERDOWN error, so try to sleep one second + * before requesting the new configuration. */ + fetch_slots = 1; + do_wait = 1; + printf("Error from server %s:%d: %s.\n", c->cluster_node->ip, c->cluster_node->port, r->str); + } + if (do_wait) sleep(1); + if (fetch_slots && !fetchClusterSlotsConfiguration(c)) + exit(1); + } else { + if (c->cluster_node) { + printf("Error from server %s:%d: %s\n", + c->cluster_node->ip, + c->cluster_node->port, + r->str); } else printf("Error from server: %s\n", r->str); - } - } - - /* Try to update slots configuration if reply error is - * MOVED/ASK/CLUSTERDOWN and the key(s) used by the command - * contain(s) the slot hash tag. */ - if (is_err && c->cluster_node && c->staglen) { - int fetch_slots = 0, do_wait = 0; - if (!strncmp(r->str,"MOVED",5) || !strncmp(r->str,"ASK",3)) - fetch_slots = 1; - else if (!strncmp(r->str,"CLUSTERDOWN",11)) { - /* Usually the cluster is able to recover itself after - * a CLUSTERDOWN error, so try to sleep one second - * before requesting the new configuration. */ - fetch_slots = 1; - do_wait = 1; - printf("Error from server %s:%d: %s\n", - c->cluster_node->ip, - c->cluster_node->port, - r->str); - } - if (do_wait) sleep(1); - if (fetch_slots && !fetchClusterSlotsConfiguration(c)) exit(1); + } } freeReplyObject(reply); @@ -1293,8 +1295,7 @@ static int fetchClusterSlotsConfiguration(client c) { atomicGetIncr(config.is_fetching_slots, is_fetching_slots, 1); if (is_fetching_slots) return -1; //TODO: use other codes || errno ? atomicSet(config.is_fetching_slots, 1); - if (config.showerrors) - printf("Cluster slots configuration changed, fetching new one...\n"); + printf("WARNING: Cluster slots configuration changed, fetching new one...\n"); const char *errmsg = "Failed to update cluster slots configuration"; static dictType dtype = { dictSdsHash, /* hash function */ @@ -1470,7 +1471,8 @@ int parseOptions(int argc, const char **argv) { } else if (!strcmp(argv[i],"-I")) { config.idlemode = 1; } else if (!strcmp(argv[i],"-e")) { - config.showerrors = 1; + printf("WARNING: -e option has been deprecated. " + "We now immediatly exit on error to avoid false results.\n"); } else if (!strcmp(argv[i],"-t")) { if (lastarg) goto invalid; /* We get the list of tests to run as a string in the form @@ -1573,8 +1575,6 @@ usage: " is executed. Default tests use this to hit random keys in the\n" " specified range.\n" " -P Pipeline requests. Default 1 (no pipeline).\n" -" -e If server replies with errors, show them on stdout.\n" -" (no more than 1 error per second is displayed)\n" " -q Quiet. Just show query/sec values\n" " --precision Number of decimal places to display in latency output (default 0)\n" " --csv Output in CSV format\n" @@ -1699,7 +1699,6 @@ int main(int argc, const char **argv) { config.keepalive = 1; config.datasize = 3; config.pipeline = 1; - config.showerrors = 0; config.randomkeys = 0; config.randomkeys_keyspacelen = 0; config.quiet = 0; @@ -1784,7 +1783,6 @@ int main(int argc, const char **argv) { getRedisConfig(config.hostip, config.hostport, config.hostsocket); if (config.redis_config == NULL) { fprintf(stderr, "WARN: could not fetch server CONFIG\n"); - exit(1); } } if (config.num_threads > 0) { From d30ee6c44a72d9a3a13167cef5914943ede0f4d2 Mon Sep 17 00:00:00 2001 From: Binbin Date: Wed, 28 Apr 2021 18:19:55 +0800 Subject: [PATCH 16/41] redis-cli: Do not use hostsocket when we got redirected in cluster mode (#8870) When redis-cli was used with both -c (cluster) and -s (unix socket), it would have kept trying to use that unix socket, even if it got redirected by the cluster (resulting in an infinite loop). (cherry picked from commit 416f2773395ffcd72d8d8408e1558f49d59a0077) --- src/redis-cli.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/redis-cli.c b/src/redis-cli.c index 7e1fe3934..ff34f2b6a 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -844,7 +844,9 @@ static int cliConnect(int flags) { cliRefreshPrompt(); } - if (config.hostsocket == NULL) { + /* Do not use hostsocket when we got redirected in cluster mode */ + if (config.hostsocket == NULL || + (config.cluster_mode && config.cluster_reissue_command)) { context = redisConnect(config.hostip,config.hostport); } else { context = redisConnectUnix(config.hostsocket); From 1eab6202ac867b3210a7827cfe7e1ac3a3a9d356 Mon Sep 17 00:00:00 2001 From: Binbin Date: Wed, 28 Apr 2021 21:03:24 +0800 Subject: [PATCH 17/41] redis-benchmark: Add zfree(data) and fix lrange size / text mismatch (#8872) missing zfree(data) in redis-benchmark. And also correct the wrong size in lrange. the text mentioned 500, but size was 450, changed to 500 (cherry picked from commit 1eff8564c78011f7257e485796990a0d4d607a5b) --- src/redis-benchmark.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/redis-benchmark.c b/src/redis-benchmark.c index 51dba9511..fa024d44f 100644 --- a/src/redis-benchmark.c +++ b/src/redis-benchmark.c @@ -1946,8 +1946,8 @@ int main(int argc, const char **argv) { } if (test_is_selected("lrange") || test_is_selected("lrange_500")) { - len = redisFormatCommand(&cmd,"LRANGE mylist%s 0 449",tag); - benchmark("LRANGE_500 (first 450 elements)",cmd,len); + len = redisFormatCommand(&cmd,"LRANGE mylist%s 0 499",tag); + benchmark("LRANGE_500 (first 500 elements)",cmd,len); free(cmd); } @@ -1974,6 +1974,7 @@ int main(int argc, const char **argv) { if (!config.csv) printf("\n"); } while(config.loop); + zfree(data); if (config.redis_config != NULL) freeRedisConfig(config.redis_config); return 0; From 42f2ad051658040c1dd484cf87adc643eebdd85d Mon Sep 17 00:00:00 2001 From: Huang Zhw Date: Thu, 29 Apr 2021 17:08:52 +0800 Subject: [PATCH 18/41] Improve redis-cli help. When help command, we only match command (#8879) prefix args not all args. So when we help commands with subcommands, all subcommands will be output. (cherry picked from commit 0b1b9edb2843730b03f78b6073cdd30873dbba95) --- src/redis-cli.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/redis-cli.c b/src/redis-cli.c index ff34f2b6a..9f88a9c88 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -663,7 +663,7 @@ static void cliOutputHelp(int argc, char **argv) { help = entry->org; if (group == -1) { /* Compare all arguments */ - if (argc == entry->argc) { + if (argc <= entry->argc) { for (j = 0; j < argc; j++) { if (strcasecmp(argv[j],entry->argv[j]) != 0) break; } From 922e3bf59f29f1acef0bc19f502a10854f25cc11 Mon Sep 17 00:00:00 2001 From: sundb Date: Sun, 2 May 2021 15:32:57 +0800 Subject: [PATCH 19/41] Fix memory leak in moduleDefragGlobals (#8853) (cherry picked from commit 5100ef9f8246dec6590f35f6b9f0b88c2dea0cfb) --- src/module.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/module.c b/src/module.c index 727cdc43f..c76241bfb 100644 --- a/src/module.c +++ b/src/module.c @@ -9205,6 +9205,7 @@ long moduleDefragGlobals(void) { module->defrag_cb(&defrag_ctx); defragged += defrag_ctx.defragged; } + dictReleaseIterator(di); return defragged; } From 046352069396fe3be0a50ca505cb65af15c0d995 Mon Sep 17 00:00:00 2001 From: Oran Agra Date: Mon, 3 May 2021 08:27:22 +0300 Subject: [PATCH 20/41] Fix integer overflow in intset (CVE-2021-29478) An integer overflow bug in Redis 6.2 could be exploited to corrupt the heap and potentially result with remote code execution. The vulnerability involves changing the default set-max-intset-entries configuration value, creating a large set key that consists of integer values and using the COPY command to duplicate it. The integer overflow bug exists in all versions of Redis starting with 2.6, where it could result with a corrupted RDB or DUMP payload, but not exploited through COPY (which did not exist before 6.2). (cherry picked from commit 29900d4e6bccdf3691bedf0ea9a5d84863fa3592) --- src/intset.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/intset.c b/src/intset.c index 1a64ecae8..9ba13898d 100644 --- a/src/intset.c +++ b/src/intset.c @@ -281,7 +281,7 @@ uint32_t intsetLen(const intset *is) { /* Return intset blob size in bytes. */ size_t intsetBlobLen(intset *is) { - return sizeof(intset)+intrev32ifbe(is->length)*intrev32ifbe(is->encoding); + return sizeof(intset)+(size_t)intrev32ifbe(is->length)*intrev32ifbe(is->encoding); } /* Validate the integrity of the data structure. From 92e3b1802f72ca0c5b0bde97f01d9b57a758d85c Mon Sep 17 00:00:00 2001 From: Oran Agra Date: Mon, 3 May 2021 08:32:31 +0300 Subject: [PATCH 21/41] Fix integer overflow in STRALGO LCS (CVE-2021-29477) An integer overflow bug in Redis version 6.0 or newer could be exploited using the STRALGO LCS command to corrupt the heap and potentially result with remote code execution. (cherry picked from commit f0c5f920d0f88bd8aa376a2c05af4902789d1ef9) --- src/t_string.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/t_string.c b/src/t_string.c index 0967e30e1..490d5983a 100644 --- a/src/t_string.c +++ b/src/t_string.c @@ -805,7 +805,7 @@ void stralgoLCS(client *c) { /* Setup an uint32_t array to store at LCS[i,j] the length of the * LCS A0..i-1, B0..j-1. Note that we have a linear array here, so * we index it as LCS[j+(blen+1)*j] */ - uint32_t *lcs = zmalloc((alen+1)*(blen+1)*sizeof(uint32_t)); + uint32_t *lcs = zmalloc((size_t)(alen+1)*(blen+1)*sizeof(uint32_t)); #define LCS(A,B) lcs[(B)+((A)*(blen+1))] /* Start building the LCS table. */ From 2df6695f2bacb6a2665d0171164d4aa6e67f6e88 Mon Sep 17 00:00:00 2001 From: Oran Agra Date: Mon, 3 May 2021 08:33:21 +0300 Subject: [PATCH 22/41] Resolve nonsense static analysis warnings (cherry picked from commit fd7d51c353607f350c865155444bce9236f3d682) --- .../jemalloc/internal/jemalloc_internal_inlines_c.h | 2 +- src/lolwut.c | 4 ++-- src/memtest.c | 2 +- src/object.c | 2 +- src/redis-check-rdb.c | 2 +- src/redis-cli.c | 6 +++--- src/sentinel.c | 12 ++++++------ 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_inlines_c.h b/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_inlines_c.h index 2685802b8..b19a94207 100644 --- a/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_inlines_c.h +++ b/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_inlines_c.h @@ -235,7 +235,7 @@ iget_defrag_hint(tsdn_t *tsdn, void* ptr) { int free_in_slab = extent_nfree_get(slab); if (free_in_slab) { const bin_info_t *bin_info = &bin_infos[binind]; - int curslabs = bin->stats.curslabs; + unsigned long curslabs = bin->stats.curslabs; size_t curregs = bin->stats.curregs; if (bin->slabcur) { /* remove slabcur from the overall utilization */ diff --git a/src/lolwut.c b/src/lolwut.c index eebd5da6a..931f311cd 100644 --- a/src/lolwut.c +++ b/src/lolwut.c @@ -94,8 +94,8 @@ lwCanvas *lwCreateCanvas(int width, int height, int bgcolor) { lwCanvas *canvas = zmalloc(sizeof(*canvas)); canvas->width = width; canvas->height = height; - canvas->pixels = zmalloc(width*height); - memset(canvas->pixels,bgcolor,width*height); + canvas->pixels = zmalloc((size_t)width*height); + memset(canvas->pixels,bgcolor,(size_t)width*height); return canvas; } diff --git a/src/memtest.c b/src/memtest.c index cb4d35e83..bc0ac3a66 100644 --- a/src/memtest.c +++ b/src/memtest.c @@ -71,7 +71,7 @@ void memtest_progress_start(char *title, int pass) { printf("\x1b[H\x1b[2K"); /* Cursor home, clear current line. */ printf("%s [%d]\n", title, pass); /* Print title. */ progress_printed = 0; - progress_full = ws.ws_col*(ws.ws_row-3); + progress_full = (size_t)ws.ws_col*(ws.ws_row-3); fflush(stdout); } diff --git a/src/object.c b/src/object.c index b75e547b9..c7b25ffd4 100644 --- a/src/object.c +++ b/src/object.c @@ -836,7 +836,7 @@ size_t objectComputeSize(robj *o, size_t sample_size) { if (samples) asize += (double)elesize/samples*dictSize(d); } else if (o->encoding == OBJ_ENCODING_INTSET) { intset *is = o->ptr; - asize = sizeof(*o)+sizeof(*is)+is->encoding*is->length; + asize = sizeof(*o)+sizeof(*is)+(size_t)is->encoding*is->length; } else { serverPanic("Unknown set encoding"); } diff --git a/src/redis-check-rdb.c b/src/redis-check-rdb.c index 4f451969a..6ddfda7ff 100644 --- a/src/redis-check-rdb.c +++ b/src/redis-check-rdb.c @@ -250,7 +250,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) { rdbstate.doing = RDB_CHECK_DOING_READ_LEN; if ((dbid = rdbLoadLen(&rdb,NULL)) == RDB_LENERR) goto eoferr; - rdbCheckInfo("Selecting DB ID %d", dbid); + rdbCheckInfo("Selecting DB ID %llu", (unsigned long long)dbid); continue; /* Read type again. */ } else if (type == RDB_OPCODE_RESIZEDB) { /* RESIZEDB: Hint about the size of the keys in the currently diff --git a/src/redis-cli.c b/src/redis-cli.c index 9f88a9c88..81be58b17 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -5483,7 +5483,7 @@ static void clusterManagerNodeArrayReset(clusterManagerNodeArray *array) { static void clusterManagerNodeArrayShift(clusterManagerNodeArray *array, clusterManagerNode **nodeptr) { - assert(array->nodes < (array->nodes + array->len)); + assert(array->len > 0); /* If the first node to be shifted is not NULL, decrement count. */ if (*array->nodes != NULL) array->count--; /* Store the first node to be shifted into 'nodeptr'. */ @@ -5496,7 +5496,7 @@ static void clusterManagerNodeArrayShift(clusterManagerNodeArray *array, static void clusterManagerNodeArrayAdd(clusterManagerNodeArray *array, clusterManagerNode *node) { - assert(array->nodes < (array->nodes + array->len)); + assert(array->len > 0); assert(node != NULL); assert(array->count < array->len); array->nodes[array->count++] = node; @@ -6873,7 +6873,7 @@ void showLatencyDistSamples(struct distsamples *samples, long long tot) { 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)); + ceil((double) 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; diff --git a/src/sentinel.c b/src/sentinel.c index a56cd8b15..2d81d98ac 100644 --- a/src/sentinel.c +++ b/src/sentinel.c @@ -4119,16 +4119,16 @@ void sentinelSetCommand(client *c) { int numargs = j-old_j+1; switch(numargs) { case 2: - sentinelEvent(LL_WARNING,"+set",ri,"%@ %s %s",c->argv[old_j]->ptr, - c->argv[old_j+1]->ptr); + sentinelEvent(LL_WARNING,"+set",ri,"%@ %s %s",(char*)c->argv[old_j]->ptr, + (char*)c->argv[old_j+1]->ptr); break; case 3: - sentinelEvent(LL_WARNING,"+set",ri,"%@ %s %s %s",c->argv[old_j]->ptr, - c->argv[old_j+1]->ptr, - c->argv[old_j+2]->ptr); + sentinelEvent(LL_WARNING,"+set",ri,"%@ %s %s %s",(char*)c->argv[old_j]->ptr, + (char*)c->argv[old_j+1]->ptr, + (char*)c->argv[old_j+2]->ptr); break; default: - sentinelEvent(LL_WARNING,"+set",ri,"%@ %s",c->argv[old_j]->ptr); + sentinelEvent(LL_WARNING,"+set",ri,"%@ %s",(char*)c->argv[old_j]->ptr); break; } } From e90e5640e7840860bc6726a08135ea86687bbd58 Mon Sep 17 00:00:00 2001 From: Oran Agra Date: Mon, 3 May 2021 12:08:20 +0300 Subject: [PATCH 23/41] Redis 6.2.3 --- 00-RELEASENOTES | 34 ++++++++++++++++++++++++++++++++++ src/version.h | 4 ++-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/00-RELEASENOTES b/00-RELEASENOTES index 8a1405e41..4f6cb9978 100644 --- a/00-RELEASENOTES +++ b/00-RELEASENOTES @@ -11,6 +11,40 @@ CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP. SECURITY: There are security fixes in the release. -------------------------------------------------------------------------------- +================================================================================ +Redis 6.2.3 Released Mon May 3 19:00:00 IST 2021 +================================================================================ + +Upgrade urgency: SECURITY, Contains fixes to security issues that affect +authenticated client connections. LOW otherwise. + +Integer overflow in STRALGO LCS command (CVE-2021-29477): +An integer overflow bug in Redis version 6.0 or newer could be exploited using +the STRALGO LCS command to corrupt the heap and potentially result in remote +code execution. The integer overflow bug exists in all versions of Redis +starting with 6.0. + +Integer overflow in COPY command for large intsets (CVE-2021-29478): +An integer overflow bug in Redis 6.2 could be exploited to corrupt the heap and +potentially result with remote code execution. The vulnerability involves +changing the default set-max-intset-entries configuration value, creating a +large set key that consists of integer values and using the COPY command to +duplicate it. The integer overflow bug exists in all versions of Redis starting +with 2.6, where it could result with a corrupted RDB or DUMP payload, but not +exploited through COPY (which did not exist before 6.2). + +Bug fixes that are only applicable to previous releases of Redis 6.2: +* Fix memory leak in moduleDefragGlobals (#8853) +* Fix memory leak when doing lazy freeing client tracking table (#8822) +* Block abusive replicas from sending command that could assert and crash redis (#8868) + +Other bug fixes: +* Use a monotonic clock to check for Lua script timeout (#8812) +* redis-cli: Do not use unix socket when we got redirected in cluster mode (#8870) + +Modules: +* Fix RM_GetClusterNodeInfo() to correctly populate master id (#8846) + ================================================================================ Redis 6.2.2 Released Mon April 19 19:00:00 IST 2021 ================================================================================ diff --git a/src/version.h b/src/version.h index 3c5dc02c5..b87f2b9c3 100644 --- a/src/version.h +++ b/src/version.h @@ -1,2 +1,2 @@ -#define REDIS_VERSION "6.2.2" -#define REDIS_VERSION_NUM 0x00060202 +#define REDIS_VERSION "6.2.3" +#define REDIS_VERSION_NUM 0x00060203 From 70602db1662de9ba3fceb4fa9002f2fe243ca048 Mon Sep 17 00:00:00 2001 From: christianEQ Date: Tue, 1 Jun 2021 20:54:48 +0000 Subject: [PATCH 24/41] working diag tool prototype Former-commit-id: fefbe96816f6a18ca6f8c8d3794502e6e610650f --- src/keydb-diagnostic-tool.cpp | 1073 +++------------------------------ 1 file changed, 98 insertions(+), 975 deletions(-) diff --git a/src/keydb-diagnostic-tool.cpp b/src/keydb-diagnostic-tool.cpp index 8dea6cdbf..c41995284 100644 --- a/src/keydb-diagnostic-tool.cpp +++ b/src/keydb-diagnostic-tool.cpp @@ -100,7 +100,7 @@ static struct config { char *auth; const char *user; int precision; - int num_threads; + int max_threads; struct benchmarkThread **threads; int cluster_mode; int cluster_node_count; @@ -140,6 +140,7 @@ typedef struct _client { int thread_id; struct clusterNode *cluster_node; int slots_last_update; + redisReply *lastReply; } *client; /* Threads. */ @@ -181,26 +182,11 @@ int g_fInCrash = false; /* Prototypes */ static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask); -static void createMissingClients(client c); static benchmarkThread *createBenchmarkThread(int index); static void freeBenchmarkThread(benchmarkThread *thread); static void freeBenchmarkThreads(); -static void *execBenchmarkThread(void *ptr); -static clusterNode *createClusterNode(char *ip, int port); -static redisConfig *getRedisConfig(const char *ip, int port, - const char *hostsocket); static redisContext *getRedisContext(const char *ip, int port, const char *hostsocket); -static void freeRedisConfig(redisConfig *cfg); -static int fetchClusterSlotsConfiguration(client c); -static void updateClusterSlotsConfiguration(); -int showThroughput(struct aeEventLoop *eventLoop, long long id, - void *clientData); - -/* Dict callbacks */ -static uint64_t dictSdsHash(const void *key); -static int dictSdsKeyCompare(void *privdata, const void *key1, - const void *key2); /* Implementation */ static long long ustime(void) { @@ -213,32 +199,6 @@ static long long ustime(void) { return ust; } -static long long mstime(void) { - struct timeval tv; - long long mst; - - gettimeofday(&tv, NULL); - mst = ((long long)tv.tv_sec)*1000; - mst += tv.tv_usec/1000; - return mst; -} - -static uint64_t dictSdsHash(const void *key) { - return dictGenHashFunction((unsigned char*)key, sdslen((char*)key)); -} - -static int dictSdsKeyCompare(void *privdata, const void *key1, - const void *key2) -{ - int l1,l2; - DICT_NOTUSED(privdata); - - l1 = sdslen((sds)key1); - l2 = sdslen((sds)key2); - if (l1 != l2) return 0; - return memcmp(key1, key2, l1) == 0; -} - /* _serverAssert is needed by dict */ extern "C" void _serverAssert(const char *estr, const char *file, int line) { fprintf(stderr, "=== ASSERTION FAILED ==="); @@ -292,58 +252,6 @@ cleanup: return NULL; } -static redisConfig *getRedisConfig(const char *ip, int port, - const char *hostsocket) -{ - redisConfig *cfg = (redisConfig*)zcalloc(sizeof(*cfg)); - if (!cfg) return NULL; - redisContext *c = NULL; - redisReply *reply = NULL, *sub_reply = NULL; - c = getRedisContext(ip, port, hostsocket); - if (c == NULL) { - freeRedisConfig(cfg); - return NULL; - } - redisAppendCommand(c, "CONFIG GET %s", "save"); - redisAppendCommand(c, "CONFIG GET %s", "appendonly"); - - void *r; - for (int i=0; i < 2; i++) { - int res = redisGetReply(c, &r); - if (reply) freeReplyObject(reply); - reply = res == REDIS_OK ? ((redisReply *) r) : NULL; - if (res != REDIS_OK || !r) goto fail; - if (reply->type == REDIS_REPLY_ERROR) { - fprintf(stderr, "ERROR: %s\n", reply->str); - goto fail; - } - if (reply->type != REDIS_REPLY_ARRAY || reply->elements < 2) goto fail; - sub_reply = reply->element[1]; - const char *value = sub_reply->str; - if (!value) value = ""; - switch (i) { - case 0: cfg->save = sdsnew(value); break; - case 1: cfg->appendonly = sdsnew(value); break; - } - } - freeReplyObject(reply); - redisFree(c); - return cfg; -fail: - fprintf(stderr, "ERROR: failed to fetch CONFIG from "); - if (hostsocket == NULL) fprintf(stderr, "%s:%d\n", ip, port); - else fprintf(stderr, "%s\n", hostsocket); - freeReplyObject(reply); - redisFree(c); - freeRedisConfig(cfg); - return NULL; -} -static void freeRedisConfig(redisConfig *cfg) { - if (cfg->save) sdsfree(cfg->save); - if (cfg->appendonly) sdsfree(cfg->appendonly); - zfree(cfg); -} - static void freeClient(client c) { aeEventLoop *el = CLIENT_GET_EVENTLOOP(c); listNode *ln; @@ -361,12 +269,12 @@ static void freeClient(client c) { zfree(c->randptr); zfree(c->stagptr); zfree(c); - if (config.num_threads) pthread_mutex_lock(&(config.liveclients_mutex)); + if (config.max_threads) pthread_mutex_lock(&(config.liveclients_mutex)); config.liveclients--; ln = listSearchKey(config.clients,c); assert(ln != NULL); listDelNode(config.clients,ln); - if (config.num_threads) pthread_mutex_unlock(&(config.liveclients_mutex)); + if (config.max_threads) pthread_mutex_unlock(&(config.liveclients_mutex)); } static void freeAllClients(void) { @@ -406,53 +314,6 @@ static void randomizeClientKey(client c) { } } -static void setClusterKeyHashTag(client c) { - assert(c->thread_id >= 0); - clusterNode *node = c->cluster_node; - assert(node); - assert(node->current_slot_index < node->slots_count); - int is_updating_slots = 0; - atomicGet(config.is_updating_slots, is_updating_slots); - /* If updateClusterSlotsConfiguration is updating the slots array, - * call updateClusterSlotsConfiguration is order to block the thread - * since the mutex is locked. When the slots will be updated by the - * thread that's actually performing the update, the execution of - * updateClusterSlotsConfiguration won't actually do anything, since - * the updated_slots_count array will be already NULL. */ - if (is_updating_slots) updateClusterSlotsConfiguration(); - int slot = node->slots[node->current_slot_index]; - const char *tag = crc16_slot_table[slot]; - int taglen = strlen(tag); - size_t i; - for (i = 0; i < c->staglen; i++) { - char *p = c->stagptr[i] + 1; - p[0] = tag[0]; - p[1] = (taglen >= 2 ? tag[1] : '}'); - p[2] = (taglen == 3 ? tag[2] : '}'); - } -} - -static void clientDone(client c) { - int requests_finished = 0; - atomicGet(config.requests_finished, requests_finished); - if (requests_finished >= config.requests) { - freeClient(c); - if (!config.num_threads && config.el) aeStop(config.el); - return; - } - if (config.keepalive) { - resetClient(c); - } else { - if (config.num_threads) pthread_mutex_lock(&(config.liveclients_mutex)); - config.liveclients--; - createMissingClients(c); - config.liveclients++; - if (config.num_threads) - pthread_mutex_unlock(&(config.liveclients_mutex)); - freeClient(c); - } -} - static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) { client c = (client)privdata; void *reply = NULL; @@ -497,29 +358,6 @@ static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) { } } - /* Try to update slots configuration if reply error is - * MOVED/ASK/CLUSTERDOWN and the key(s) used by the command - * contain(s) the slot hash tag. */ - if (is_err && c->cluster_node && c->staglen) { - int fetch_slots = 0, do_wait = 0; - if (!strncmp(r->str,"MOVED",5) || !strncmp(r->str,"ASK",3)) - fetch_slots = 1; - else if (!strncmp(r->str,"CLUSTERDOWN",11)) { - /* Usually the cluster is able to recover itself after - * a CLUSTERDOWN error, so try to sleep one second - * before requesting the new configuration. */ - fetch_slots = 1; - do_wait = 1; - printf("Error from server %s:%d: %s\n", - c->cluster_node->ip, - c->cluster_node->port, - r->str); - } - if (do_wait) sleep(1); - if (fetch_slots && !fetchClusterSlotsConfiguration(c)) - exit(1); - } - freeReplyObject(reply); /* This is an OK for prefix commands such as auth and select.*/ if (c->prefix_pending > 0) { @@ -543,7 +381,7 @@ static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) { config.latency[requests_finished] = c->latency; c->pending--; if (c->pending == 0) { - clientDone(c); + resetClient(c); break; } } else { @@ -571,7 +409,6 @@ static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) { /* Really initialize: randomize keys and set start time. */ if (config.randomkeys) randomizeClientKey(c); - if (config.cluster_mode && c->staglen > 0) setClusterKeyHashTag(c); atomicGet(config.slots_last_update, c->slots_last_update); c->start = ustime(); c->latency = -1; @@ -628,7 +465,7 @@ static client createClient(const char *cmd, size_t len, client from, int thread_ port = config.hostport; } else { int node_idx = 0; - if (config.num_threads < config.cluster_node_count) + if (config.max_threads < config.cluster_node_count) node_idx = config.liveclients % config.cluster_node_count; else node_idx = thread_id % config.cluster_node_count; @@ -783,149 +620,16 @@ static client createClient(const char *cmd, size_t len, client from, int thread_ return c; } -static void createMissingClients(client c) { - int n = 0; - while(config.liveclients < config.numclients) { - int thread_id = -1; - if (config.num_threads) - thread_id = config.liveclients % config.num_threads; - createClient(NULL,0,c,thread_id); - - /* Listen backlog is quite limited on most systems */ - if (++n > 64) { - usleep(50000); - n = 0; - } - } -} - -static int compareLatency(const void *a, const void *b) { - return (*(long long*)a)-(*(long long*)b); -} - -static int ipow(int base, int exp) { - int result = 1; - while (exp) { - if (exp & 1) result *= base; - exp /= 2; - base *= base; - } - return result; -} - -static void showLatencyReport(void) { - int i, curlat = 0; - int usbetweenlat = ipow(10, MAX_LATENCY_PRECISION-config.precision); - float perc, reqpersec; - - reqpersec = (float)config.requests_finished/((float)config.totlatency/1000); - if (!config.quiet && !config.csv) { - printf("====== %s ======\n", config.title); - printf(" %d requests completed in %.2f seconds\n", config.requests_finished, - (float)config.totlatency/1000); - printf(" %d parallel clients\n", config.numclients); - printf(" %d bytes payload\n", config.datasize); - printf(" keep alive: %d\n", config.keepalive); - if (config.cluster_mode) { - printf(" cluster mode: yes (%d masters)\n", - config.cluster_node_count); - int m ; - for (m = 0; m < config.cluster_node_count; m++) { - clusterNode *node = config.cluster_nodes[m]; - redisConfig *cfg = node->redis_config; - if (cfg == NULL) continue; - printf(" node [%d] configuration:\n",m ); - printf(" save: %s\n", - sdslen(cfg->save) ? cfg->save : "NONE"); - printf(" appendonly: %s\n", cfg->appendonly); - } - } else { - if (config.redis_config) { - printf(" host configuration \"save\": %s\n", - config.redis_config->save); - printf(" host configuration \"appendonly\": %s\n", - config.redis_config->appendonly); - } - } - printf(" multi-thread: %s\n", (config.num_threads ? "yes" : "no")); - if (config.num_threads) - printf(" threads: %d\n", config.num_threads); - - printf("\n"); - - qsort(config.latency,config.requests,sizeof(long long),compareLatency); - for (i = 0; i < config.requests; i++) { - if (config.latency[i]/usbetweenlat != curlat || - i == (config.requests-1)) - { - /* After the 2 milliseconds latency to have percentages split - * by decimals will just add a lot of noise to the output. */ - if (config.latency[i] >= 2000) { - config.precision = 0; - usbetweenlat = ipow(10, - MAX_LATENCY_PRECISION-config.precision); - } - - curlat = config.latency[i]/usbetweenlat; - perc = ((float)(i+1)*100)/config.requests; - printf("%.2f%% <= %.*f milliseconds\n", perc, config.precision, - curlat/pow(10.0, config.precision)); - } - } - printf("%.2f requests per second\n\n", reqpersec); - } else if (config.csv) { - printf("\"%s\",\"%.2f\"\n", config.title, reqpersec); - } else { - printf("%s: %.2f requests per second\n", config.title, reqpersec); - } -} - static void initBenchmarkThreads() { int i; if (config.threads) freeBenchmarkThreads(); - config.threads = (benchmarkThread**)zmalloc(config.num_threads * sizeof(benchmarkThread*), MALLOC_LOCAL); - for (i = 0; i < config.num_threads; i++) { + config.threads = (benchmarkThread**)zmalloc(config.max_threads * sizeof(benchmarkThread*), MALLOC_LOCAL); + for (i = 0; i < config.max_threads; i++) { benchmarkThread *thread = createBenchmarkThread(i); config.threads[i] = thread; } } -static void startBenchmarkThreads() { - int i; - for (i = 0; i < config.num_threads; i++) { - benchmarkThread *t = config.threads[i]; - if (pthread_create(&(t->thread), NULL, execBenchmarkThread, t)){ - fprintf(stderr, "FATAL: Failed to start thread %d.\n", i); - exit(1); - } - } - for (i = 0; i < config.num_threads; i++) - pthread_join(config.threads[i]->thread, NULL); -} - -static void benchmark(const char *title, const char *cmd, int len) { - client c; - - config.title = title; - config.requests_issued = 0; - config.requests_finished = 0; - - if (config.num_threads) initBenchmarkThreads(); - - int thread_id = config.num_threads > 0 ? 0 : -1; - c = createClient(cmd,len,NULL,thread_id); - createMissingClients(c); - - config.start = mstime(); - if (!config.num_threads) aeMain(config.el); - else startBenchmarkThreads(); - config.totlatency = mstime()-config.start; - - showLatencyReport(); - freeAllClients(); - if (config.threads) freeBenchmarkThreads(); -} - /* Thread functions. */ static benchmarkThread *createBenchmarkThread(int index) { @@ -933,7 +637,6 @@ static benchmarkThread *createBenchmarkThread(int index) { if (thread == NULL) return NULL; thread->index = index; thread->el = aeCreateEventLoop(1024*10); - aeCreateTimeEvent(thread->el,1,showThroughput,NULL,NULL); return thread; } @@ -944,7 +647,7 @@ static void freeBenchmarkThread(benchmarkThread *thread) { static void freeBenchmarkThreads() { int i = 0; - for (; i < config.num_threads; i++) { + for (; i < config.max_threads; i++) { benchmarkThread *thread = config.threads[i]; if (thread) freeBenchmarkThread(thread); } @@ -958,360 +661,40 @@ static void *execBenchmarkThread(void *ptr) { return NULL; } -/* Cluster helper functions. */ - -static clusterNode *createClusterNode(char *ip, int port) { - clusterNode *node = (clusterNode*)zmalloc(sizeof(*node), MALLOC_LOCAL); - if (!node) return NULL; - node->ip = ip; - node->port = port; - node->name = NULL; - node->flags = 0; - node->replicate = NULL; - node->replicas_count = 0; - node->slots = (int*)zmalloc(CLUSTER_SLOTS * sizeof(int), MALLOC_LOCAL); - node->slots_count = 0; - node->current_slot_index = 0; - node->updated_slots = NULL; - node->updated_slots_count = 0; - node->migrating = NULL; - node->importing = NULL; - node->migrating_count = 0; - node->importing_count = 0; - node->redis_config = NULL; - return node; -} - -static void freeClusterNode(clusterNode *node) { - int i; - if (node->name) sdsfree(node->name); - if (node->replicate) sdsfree(node->replicate); - if (node->migrating != NULL) { - for (i = 0; i < node->migrating_count; i++) sdsfree(node->migrating[i]); - zfree(node->migrating); - } - if (node->importing != NULL) { - for (i = 0; i < node->importing_count; i++) sdsfree(node->importing[i]); - zfree(node->importing); - } - /* If the node is not the reference node, that uses the address from - * config.hostip and config.hostport, then the node ip has been - * allocated by fetchClusterConfiguration, so it must be freed. */ - if (node->ip && strcmp(node->ip, config.hostip) != 0) sdsfree(node->ip); - if (node->redis_config != NULL) freeRedisConfig(node->redis_config); - zfree(node->slots); - zfree(node); -} - -static void freeClusterNodes() { - int i = 0; - for (; i < config.cluster_node_count; i++) { - clusterNode *n = config.cluster_nodes[i]; - if (n) freeClusterNode(n); - } - zfree(config.cluster_nodes); +void initConfigDefaults() { + config.numclients = 50; + config.requests = 100000; + config.liveclients = 0; + config.el = aeCreateEventLoop(1024*10); + config.keepalive = 1; + config.datasize = 3; + config.pipeline = 1; + config.showerrors = 0; + config.randomkeys = 0; + config.randomkeys_keyspacelen = 0; + config.quiet = 0; + config.csv = 0; + config.loop = 0; + config.idlemode = 0; + config.latency = NULL; + config.clients = listCreate(); + config.hostip = "127.0.0.1"; + config.hostport = 6379; + config.hostsocket = NULL; + config.tests = NULL; + config.dbnum = 0; + config.auth = NULL; + config.precision = 1; + config.max_threads = MAX_THREADS; + config.threads = NULL; + config.cluster_mode = 0; + config.cluster_node_count = 0; config.cluster_nodes = NULL; -} - -static clusterNode **addClusterNode(clusterNode *node) { - int count = config.cluster_node_count + 1; - config.cluster_nodes = (clusterNode**)zrealloc(config.cluster_nodes, - count * sizeof(*node), MALLOC_LOCAL); - if (!config.cluster_nodes) return NULL; - config.cluster_nodes[config.cluster_node_count++] = node; - return config.cluster_nodes; -} - -static int fetchClusterConfiguration() { - int success = 1; - redisContext *ctx = NULL; - redisReply *reply = NULL; - char *lines = NULL; - char *line = NULL; - char *p = NULL; - ctx = getRedisContext(config.hostip, config.hostport, config.hostsocket); - if (ctx == NULL) { - exit(1); - } - clusterNode *firstNode = createClusterNode((char *) config.hostip, - config.hostport); - if (!firstNode) {success = 0; goto cleanup;} - reply = (redisReply*)redisCommand(ctx, "CLUSTER NODES"); - success = (reply != NULL); - if (!success) goto cleanup; - success = (reply->type != REDIS_REPLY_ERROR); - if (!success) { - if (config.hostsocket == NULL) { - fprintf(stderr, "Cluster node %s:%d replied with error:\n%s\n", - config.hostip, config.hostport, reply->str); - } else { - fprintf(stderr, "Cluster node %s replied with error:\n%s\n", - config.hostsocket, reply->str); - } - goto cleanup; - } - lines = reply->str; - while ((p = strstr(lines, "\n")) != NULL) { - *p = '\0'; - line = lines; - lines = p + 1; - char *name = NULL, *addr = NULL, *flags = NULL, *master_id = NULL; - int i = 0; - while ((p = strchr(line, ' ')) != NULL) { - *p = '\0'; - char *token = line; - line = p + 1; - switch(i++){ - case 0: name = token; break; - case 1: addr = token; break; - case 2: flags = token; break; - case 3: master_id = token; break; - } - if (i == 8) break; // Slots - } - if (!flags) { - fprintf(stderr, "Invalid CLUSTER NODES reply: missing flags.\n"); - success = 0; - goto cleanup; - } - int myself = (strstr(flags, "myself") != NULL); - int is_replica = (strstr(flags, "slave") != NULL || - (master_id != NULL && master_id[0] != '-')); - if (is_replica) continue; - if (addr == NULL) { - fprintf(stderr, "Invalid CLUSTER NODES reply: missing addr.\n"); - success = 0; - goto cleanup; - } - clusterNode *node = NULL; - char *ip = NULL; - int port = 0; - char *paddr = strchr(addr, ':'); - if (paddr != NULL) { - *paddr = '\0'; - ip = addr; - addr = paddr + 1; - /* If internal bus is specified, then just drop it. */ - if ((paddr = strchr(addr, '@')) != NULL) *paddr = '\0'; - port = atoi(addr); - } - if (myself) { - node = firstNode; - if (node->ip == NULL && ip != NULL) { - node->ip = ip; - node->port = port; - } - } else { - node = createClusterNode(sdsnew(ip), port); - } - if (node == NULL) { - success = 0; - goto cleanup; - } - if (name != NULL) node->name = sdsnew(name); - if (i == 8) { - int remaining = strlen(line); - while (remaining > 0) { - p = strchr(line, ' '); - if (p == NULL) p = line + remaining; - remaining -= (p - line); - - char *slotsdef = line; - *p = '\0'; - if (remaining) { - line = p + 1; - remaining--; - } else line = p; - char *dash = NULL; - if (slotsdef[0] == '[') { - slotsdef++; - if ((p = strstr(slotsdef, "->-"))) { // Migrating - *p = '\0'; - p += 3; - char *closing_bracket = strchr(p, ']'); - if (closing_bracket) *closing_bracket = '\0'; - sds slot = sdsnew(slotsdef); - sds dst = sdsnew(p); - node->migrating_count += 2; - node->migrating = - (char**)zrealloc(node->migrating, - (node->migrating_count * sizeof(sds)), MALLOC_LOCAL); - node->migrating[node->migrating_count - 2] = - slot; - node->migrating[node->migrating_count - 1] = - dst; - } else if ((p = strstr(slotsdef, "-<-"))) {//Importing - *p = '\0'; - p += 3; - char *closing_bracket = strchr(p, ']'); - if (closing_bracket) *closing_bracket = '\0'; - sds slot = sdsnew(slotsdef); - sds src = sdsnew(p); - node->importing_count += 2; - node->importing = (char**)zrealloc(node->importing, - (node->importing_count * sizeof(sds)), MALLOC_LOCAL); - node->importing[node->importing_count - 2] = - slot; - node->importing[node->importing_count - 1] = - src; - } - } else if ((dash = strchr(slotsdef, '-')) != NULL) { - p = dash; - int start, stop; - *p = '\0'; - start = atoi(slotsdef); - stop = atoi(p + 1); - while (start <= stop) { - int slot = start++; - node->slots[node->slots_count++] = slot; - } - } else if (p > slotsdef) { - int slot = atoi(slotsdef); - node->slots[node->slots_count++] = slot; - } - } - } - if (node->slots_count == 0) { - printf("WARNING: master node %s:%d has no slots, skipping...\n", - node->ip, node->port); - continue; - } - if (!addClusterNode(node)) { - success = 0; - goto cleanup; - } - } -cleanup: - if (ctx) redisFree(ctx); - if (!success) { - if (config.cluster_nodes) freeClusterNodes(); - } - if (reply) freeReplyObject(reply); - return success; -} - -/* Request the current cluster slots configuration by calling CLUSTER SLOTS - * and atomically update the slots after a successful reply. */ -static int fetchClusterSlotsConfiguration(client c) { - UNUSED(c); - int success = 1, is_fetching_slots = 0, last_update = 0; - size_t i; - atomicGet(config.slots_last_update, last_update); - if (c->slots_last_update < last_update) { - c->slots_last_update = last_update; - return -1; - } - redisReply *reply = NULL; - atomicGetIncr(config.is_fetching_slots, is_fetching_slots, 1); - if (is_fetching_slots) return -1; //TODO: use other codes || errno ? - atomicSet(config.is_fetching_slots, 1); - if (config.showerrors) - printf("Cluster slots configuration changed, fetching new one...\n"); - const char *errmsg = "Failed to update cluster slots configuration"; - static dictType dtype = { - dictSdsHash, /* hash function */ - NULL, /* key dup */ - NULL, /* val dup */ - dictSdsKeyCompare, /* key compare */ - NULL, /* key destructor */ - NULL /* val destructor */ - }; - /* printf("[%d] fetchClusterSlotsConfiguration\n", c->thread_id); */ - dict *masters = dictCreate(&dtype, NULL); - redisContext *ctx = NULL; - for (i = 0; i < (size_t) config.cluster_node_count; i++) { - clusterNode *node = config.cluster_nodes[i]; - assert(node->ip != NULL); - assert(node->name != NULL); - assert(node->port); - /* Use first node as entry point to connect to. */ - if (ctx == NULL) { - ctx = getRedisContext(node->ip, node->port, NULL); - if (!ctx) { - success = 0; - goto cleanup; - } - } - if (node->updated_slots != NULL) - zfree(node->updated_slots); - node->updated_slots = NULL; - node->updated_slots_count = 0; - dictReplace(masters, node->name, node) ; - } - reply = (redisReply*)redisCommand(ctx, "CLUSTER SLOTS"); - if (reply == NULL || reply->type == REDIS_REPLY_ERROR) { - success = 0; - if (reply) - fprintf(stderr,"%s\nCLUSTER SLOTS ERROR: %s\n",errmsg,reply->str); - goto cleanup; - } - assert(reply->type == REDIS_REPLY_ARRAY); - for (i = 0; i < reply->elements; i++) { - redisReply *r = reply->element[i]; - assert(r->type == REDIS_REPLY_ARRAY); - assert(r->elements >= 3); - int from, to, slot; - from = r->element[0]->integer; - to = r->element[1]->integer; - redisReply *nr = r->element[2]; - assert(nr->type == REDIS_REPLY_ARRAY && nr->elements >= 3); - assert(nr->element[2]->str != NULL); - sds name = sdsnew(nr->element[2]->str); - dictEntry *entry = dictFind(masters, name); - if (entry == NULL) { - success = 0; - fprintf(stderr, "%s: could not find node with ID %s in current " - "configuration.\n", errmsg, name); - if (name) sdsfree(name); - goto cleanup; - } - sdsfree(name); - clusterNode *node = (clusterNode*)dictGetVal(entry); - if (node->updated_slots == NULL) - node->updated_slots = (int*)zcalloc(CLUSTER_SLOTS * sizeof(int), MALLOC_LOCAL); - for (slot = from; slot <= to; slot++) - node->updated_slots[node->updated_slots_count++] = slot; - } - updateClusterSlotsConfiguration(); -cleanup: - freeReplyObject(reply); - redisFree(ctx); - dictRelease(masters); - atomicSet(config.is_fetching_slots, 0); - return success; -} - -/* Atomically update the new slots configuration. */ -static void updateClusterSlotsConfiguration() { - pthread_mutex_lock(&config.is_updating_slots_mutex); - atomicSet(config.is_updating_slots, 1); - int i; - for (i = 0; i < config.cluster_node_count; i++) { - clusterNode *node = config.cluster_nodes[i]; - if (node->updated_slots != NULL) { - int *oldslots = node->slots; - node->slots = node->updated_slots; - node->slots_count = node->updated_slots_count; - node->current_slot_index = 0; - node->updated_slots = NULL; - node->updated_slots_count = 0; - zfree(oldslots); - } - } - atomicSet(config.is_updating_slots, 0); - atomicIncr(config.slots_last_update, 1); - pthread_mutex_unlock(&config.is_updating_slots_mutex); -} - -/* Generate random data for redis benchmark. See #7196. */ -static void genBenchmarkRandomData(char *data, int count) { - static uint32_t state = 1234; - int i = 0; - - while (count--) { - state = (state*1103515245+12345); - data[i++] = '0'+((state>>16)&63); - } + config.redis_config = NULL; + config.is_fetching_slots = 0; + config.is_updating_slots = 0; + config.slots_last_update = 0; + config.enable_tracking = 0; } /* Returns number of consumed options. */ @@ -1399,12 +782,12 @@ int parseOptions(int argc, const char **argv) { if (config.precision > MAX_LATENCY_PRECISION) config.precision = MAX_LATENCY_PRECISION; } else if (!strcmp(argv[i],"--threads")) { if (lastarg) goto invalid; - config.num_threads = atoi(argv[++i]); - if (config.num_threads > MAX_THREADS) { + config.max_threads = atoi(argv[++i]); + if (config.max_threads > MAX_THREADS) { printf("WARNING: too many threads, limiting threads to %d.\n", MAX_THREADS); - config.num_threads = MAX_THREADS; - } else if (config.num_threads < 0) config.num_threads = 0; + config.max_threads = MAX_THREADS; + } else if (config.max_threads < 0) config.max_threads = 0; } else if (!strcmp(argv[i],"--cluster")) { config.cluster_mode = 1; } else if (!strcmp(argv[i],"--enable-tracking")) { @@ -1478,98 +861,26 @@ usage: exit(exit_status); } -int showThroughput(struct aeEventLoop *eventLoop, long long id, void *clientData) { - UNUSED(eventLoop); - UNUSED(id); - UNUSED(clientData); - int liveclients = 0; - int requests_finished = 0; - atomicGet(config.liveclients, liveclients); - atomicGet(config.requests_finished, requests_finished); - - if (liveclients == 0 && requests_finished != config.requests) { - fprintf(stderr,"All clients disconnected... aborting.\n"); - exit(1); - } - if (config.num_threads && requests_finished >= config.requests) { - aeStop(eventLoop); - return AE_NOMORE; - } - if (config.csv) return 250; - if (config.idlemode == 1) { - printf("clients: %d\r", config.liveclients); - fflush(stdout); - return 250; - } - float dt = (float)(mstime()-config.start)/1000.0; - float rps = (float)requests_finished/dt; - printf("%s: %.2f\r", config.title, rps); - fflush(stdout); - return 250; /* every 250ms */ -} - -/* Return true if the named test was selected using the -t command line - * switch, or if all the tests are selected (no -t passed by user). */ -int test_is_selected(const char *name) { - char buf[256]; - int l = strlen(name); - - if (config.tests == NULL) return 1; - buf[0] = ','; - memcpy(buf+1,name,l); - buf[l+1] = ','; - buf[l+2] = '\0'; - return strstr(config.tests,buf) != NULL; +int extractPropertyFromInfo(const char *info, const char *key, double &val) { + char *line = strstr((char*)info, key); + if (line == nullptr) return 1; + line += strlen(key) + 1; // Skip past key name and following colon + char *newline = strchr(line, '\n'); + *newline = 0; // Terminate string after relevant line + val = strtod(line, nullptr); + return 0; } int main(int argc, const char **argv) { int i; - char *data, *cmd; - const char *tag; - int len; - - client c; - + storage_init(NULL, 0); srandom(time(NULL)); signal(SIGHUP, SIG_IGN); signal(SIGPIPE, SIG_IGN); - config.numclients = 50; - config.requests = 100000; - config.liveclients = 0; - config.el = aeCreateEventLoop(1024*10); - aeCreateTimeEvent(config.el,1,showThroughput,NULL,NULL); - config.keepalive = 1; - config.datasize = 3; - config.pipeline = 1; - config.showerrors = 0; - config.randomkeys = 0; - config.randomkeys_keyspacelen = 0; - config.quiet = 0; - config.csv = 0; - config.loop = 0; - config.idlemode = 0; - config.latency = NULL; - config.clients = listCreate(); - config.hostip = "127.0.0.1"; - config.hostport = 6379; - config.hostsocket = NULL; - config.tests = NULL; - config.dbnum = 0; - config.auth = NULL; - config.precision = 1; - config.num_threads = 0; - config.threads = NULL; - config.cluster_mode = 0; - config.cluster_node_count = 0; - config.cluster_nodes = NULL; - config.redis_config = NULL; - config.is_fetching_slots = 0; - config.is_updating_slots = 0; - config.slots_last_update = 0; - config.enable_tracking = 0; + initConfigDefaults(); i = parseOptions(argc,argv); argc -= i; @@ -1577,58 +888,7 @@ int main(int argc, const char **argv) { config.latency = (long long*)zmalloc(sizeof(long long)*config.requests, MALLOC_LOCAL); - tag = ""; - - if (config.cluster_mode) { - // We only include the slot placeholder {tag} if cluster mode is enabled - tag = ":{tag}"; - - /* Fetch cluster configuration. */ - if (!fetchClusterConfiguration() || !config.cluster_nodes) { - if (!config.hostsocket) { - fprintf(stderr, "Failed to fetch cluster configuration from " - "%s:%d\n", config.hostip, config.hostport); - } else { - fprintf(stderr, "Failed to fetch cluster configuration from " - "%s\n", config.hostsocket); - } - exit(1); - } - if (config.cluster_node_count <= 1) { - fprintf(stderr, "Invalid cluster: %d node(s).\n", - config.cluster_node_count); - exit(1); - } - printf("Cluster has %d master nodes:\n\n", config.cluster_node_count); - int i = 0; - for (; i < config.cluster_node_count; i++) { - clusterNode *node = config.cluster_nodes[i]; - if (!node) { - fprintf(stderr, "Invalid cluster node #%d\n", i); - exit(1); - } - printf("Master %d: ", i); - if (node->name) printf("%s ", node->name); - printf("%s:%d\n", node->ip, node->port); - node->redis_config = getRedisConfig(node->ip, node->port, NULL); - if (node->redis_config == NULL) { - fprintf(stderr, "WARN: could not fetch node CONFIG %s:%d\n", - node->ip, node->port); - } - } - printf("\n"); - /* Automatically set thread number to node count if not specified - * by the user. */ - if (config.num_threads == 0) - config.num_threads = config.cluster_node_count; - } else { - config.redis_config = - getRedisConfig(config.hostip, config.hostport, config.hostsocket); - if (config.redis_config == NULL) - fprintf(stderr, "WARN: could not fetch server CONFIG\n"); - } - - if (config.num_threads > 0) { + if (config.max_threads > 0) { int err = 0; err |= pthread_mutex_init(&(config.requests_issued_mutex), NULL); err |= pthread_mutex_init(&(config.requests_finished_mutex), NULL); @@ -1644,187 +904,50 @@ int main(int argc, const char **argv) { } } - if (config.keepalive == 0) { - printf("WARNING: keepalive disabled, you probably need 'echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse' for Linux and 'sudo sysctl -w net.inet.tcp.msl=1000' for Mac OS X in order to use a lot of clients/requests\n"); + const char *set_value = "abcdefghijklmnopqrstuvwxyz"; + int threads_used = 0; + char command[63]; + + initBenchmarkThreads(); + redisContext *ctx = getRedisContext(config.hostip, config.hostport, config.hostsocket); + double cpu_usage; + + while (threads_used < config.max_threads) { + printf("Creating clients for thread %d...\n", threads_used); + for (int i = 0; i < config.numclients; i++) { + sprintf(command, "SET %d %s\r\n", threads_used * config.numclients + i, set_value); + createClient(command, strlen(command), NULL,threads_used); + } + + printf("Starting thread %d\n", threads_used); + + benchmarkThread *t = config.threads[threads_used]; + if (pthread_create(&(t->thread), NULL, execBenchmarkThread, t)){ + fprintf(stderr, "FATAL: Failed to start thread %d.\n", threads_used); + exit(1); + } + threads_used++; + + sleep(1); + + redisReply *reply = (redisReply*)redisCommand(ctx, "INFO"); + if (reply->type != REDIS_REPLY_STRING) { + freeReplyObject(reply); + printf("Error executing INFO command. Exiting.\r\n"); + break; + } + if (extractPropertyFromInfo(reply->str, "used_cpu_sys", cpu_usage)) { + printf("Error reading CPU usage from INFO command. Exiting.\r\n"); + break; + } + printf("CPU Usage: %f\r\n", cpu_usage); + freeReplyObject(reply); } - if (config.idlemode) { - printf("Creating %d idle connections and waiting forever (Ctrl+C when done)\n", config.numclients); - int thread_id = -1, use_threads = (config.num_threads > 0); - if (use_threads) { - thread_id = 0; - initBenchmarkThreads(); - } - c = createClient("",0,NULL,thread_id); /* will never receive a reply */ - createMissingClients(c); - if (use_threads) startBenchmarkThreads(); - else aeMain(config.el); - /* and will wait for every */ - } + printf("Done.\n"); - /* Run benchmark with command in the remainder of the arguments. */ - if (argc) { - sds title = sdsnew(argv[0]); - for (i = 1; i < argc; i++) { - title = sdscatlen(title, " ", 1); - title = sdscatlen(title, (char*)argv[i], strlen(argv[i])); - } - - do { - len = redisFormatCommandArgv(&cmd,argc,argv,NULL); - benchmark(title,cmd,len); - free(cmd); - } while(config.loop); - - if (config.redis_config != NULL) freeRedisConfig(config.redis_config); - return 0; - } - - /* Run default benchmark suite. */ - data = (char*)zmalloc(config.datasize+1, MALLOC_LOCAL); - do { - genBenchmarkRandomData(data, config.datasize); - data[config.datasize] = '\0'; - - if (test_is_selected("ping_inline") || test_is_selected("ping")) - benchmark("PING_INLINE","PING\r\n",6); - - if (test_is_selected("ping_mbulk") || test_is_selected("ping")) { - len = redisFormatCommand(&cmd,"PING"); - benchmark("PING_BULK",cmd,len); - free(cmd); - } - - if (test_is_selected("set")) { - len = redisFormatCommand(&cmd,"SET key%s:__rand_int__ %s",tag,data); - benchmark("SET",cmd,len); - free(cmd); - } - - if (test_is_selected("get")) { - len = redisFormatCommand(&cmd,"GET key%s:__rand_int__",tag); - benchmark("GET",cmd,len); - free(cmd); - } - - if (test_is_selected("incr")) { - len = redisFormatCommand(&cmd,"INCR counter%s:__rand_int__",tag); - benchmark("INCR",cmd,len); - free(cmd); - } - - if (test_is_selected("lpush")) { - len = redisFormatCommand(&cmd,"LPUSH mylist%s %s",tag,data); - benchmark("LPUSH",cmd,len); - free(cmd); - } - - if (test_is_selected("rpush")) { - len = redisFormatCommand(&cmd,"RPUSH mylist%s %s",tag,data); - benchmark("RPUSH",cmd,len); - free(cmd); - } - - if (test_is_selected("lpop")) { - len = redisFormatCommand(&cmd,"LPOP mylist%s",tag); - benchmark("LPOP",cmd,len); - free(cmd); - } - - if (test_is_selected("rpop")) { - len = redisFormatCommand(&cmd,"RPOP mylist%s",tag); - benchmark("RPOP",cmd,len); - free(cmd); - } - - if (test_is_selected("sadd")) { - len = redisFormatCommand(&cmd, - "SADD myset%s element:__rand_int__",tag); - benchmark("SADD",cmd,len); - free(cmd); - } - - if (test_is_selected("hset")) { - len = redisFormatCommand(&cmd, - "HSET myhash%s element:__rand_int__ %s",tag,data); - benchmark("HSET",cmd,len); - free(cmd); - } - - if (test_is_selected("spop")) { - len = redisFormatCommand(&cmd,"SPOP myset%s",tag); - benchmark("SPOP",cmd,len); - free(cmd); - } - - if (test_is_selected("zadd")) { - const char *score = "0"; - if (config.randomkeys) score = "__rand_int__"; - len = redisFormatCommand(&cmd, - "ZADD myzset%s %s element:__rand_int__",tag,score); - benchmark("ZADD",cmd,len); - free(cmd); - } - - if (test_is_selected("zpopmin")) { - len = redisFormatCommand(&cmd,"ZPOPMIN myzset%s",tag); - benchmark("ZPOPMIN",cmd,len); - free(cmd); - } - - if (test_is_selected("lrange") || - test_is_selected("lrange_100") || - test_is_selected("lrange_300") || - test_is_selected("lrange_500") || - test_is_selected("lrange_600")) - { - len = redisFormatCommand(&cmd,"LPUSH mylist%s %s",tag,data); - benchmark("LPUSH (needed to benchmark LRANGE)",cmd,len); - free(cmd); - } - - if (test_is_selected("lrange") || test_is_selected("lrange_100")) { - len = redisFormatCommand(&cmd,"LRANGE mylist%s 0 99",tag); - benchmark("LRANGE_100 (first 100 elements)",cmd,len); - free(cmd); - } - - if (test_is_selected("lrange") || test_is_selected("lrange_300")) { - len = redisFormatCommand(&cmd,"LRANGE mylist%s 0 299",tag); - benchmark("LRANGE_300 (first 300 elements)",cmd,len); - free(cmd); - } - - if (test_is_selected("lrange") || test_is_selected("lrange_500")) { - len = redisFormatCommand(&cmd,"LRANGE mylist%s 0 449",tag); - benchmark("LRANGE_500 (first 450 elements)",cmd,len); - free(cmd); - } - - if (test_is_selected("lrange") || test_is_selected("lrange_600")) { - len = redisFormatCommand(&cmd,"LRANGE mylist%s 0 599",tag); - benchmark("LRANGE_600 (first 600 elements)",cmd,len); - free(cmd); - } - - if (test_is_selected("mset")) { - const char *cmd_argv[21]; - cmd_argv[0] = "MSET"; - sds key_placeholder = sdscatprintf(sdsnew(""),"key%s:__rand_int__",tag); - for (i = 1; i < 21; i += 2) { - cmd_argv[i] = key_placeholder; - cmd_argv[i+1] = data; - } - len = redisFormatCommandArgv(&cmd,21,cmd_argv,NULL); - benchmark("MSET (10 keys)",cmd,len); - free(cmd); - sdsfree(key_placeholder); - } - - if (!config.csv) printf("\n"); - } while(config.loop); - - if (config.redis_config != NULL) freeRedisConfig(config.redis_config); + freeAllClients(); + freeBenchmarkThreads(); return 0; } From 46592a4089b1ec3a2717a255e2dad18b102925c2 Mon Sep 17 00:00:00 2001 From: christianEQ Date: Fri, 11 Jun 2021 16:28:29 +0000 Subject: [PATCH 25/41] don't enforce upper limit for requests issued; tool will run indefinitely Former-commit-id: 239d22ed722357f0973c971b998b21f4f7b3b1da --- src/keydb-diagnostic-tool.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/keydb-diagnostic-tool.cpp b/src/keydb-diagnostic-tool.cpp index c41995284..3dced8523 100644 --- a/src/keydb-diagnostic-tool.cpp +++ b/src/keydb-diagnostic-tool.cpp @@ -399,14 +399,6 @@ static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) { /* Initialize request when nothing was written. */ if (c->written == 0) { - /* Enforce upper bound to number of requests. */ - int requests_issued = 0; - atomicGetIncr(config.requests_issued, requests_issued, 1); - if (requests_issued >= config.requests) { - freeClient(c); - return; - } - /* Really initialize: randomize keys and set start time. */ if (config.randomkeys) randomizeClientKey(c); atomicGet(config.slots_last_update, c->slots_last_update); @@ -913,7 +905,7 @@ int main(int argc, const char **argv) { double cpu_usage; while (threads_used < config.max_threads) { - printf("Creating clients for thread %d...\n", threads_used); + printf("Creating %d clients for thread %d...\n", config.numclients, threads_used); for (int i = 0; i < config.numclients; i++) { sprintf(command, "SET %d %s\r\n", threads_used * config.numclients + i, set_value); createClient(command, strlen(command), NULL,threads_used); From 40a5f86dadc6106fb464695c234a6e6171e6e74e Mon Sep 17 00:00:00 2001 From: John Sully Date: Mon, 14 Jun 2021 16:32:47 +0000 Subject: [PATCH 26/41] active defrag tests need to run single threaded because jemalloc has seperate mempools per thread and the numbers won't match otherwise Former-commit-id: 3a1d3090f2ec5a442e3a7c192987cdfa24094145 --- tests/unit/memefficiency.tcl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/memefficiency.tcl b/tests/unit/memefficiency.tcl index d5c2feb4f..2a2db72cd 100644 --- a/tests/unit/memefficiency.tcl +++ b/tests/unit/memefficiency.tcl @@ -395,7 +395,7 @@ start_server {tags {"defrag"} overrides {appendonly yes auto-aof-rewrite-percent # if the current slab is lower in utilization the defragger would have ended up in stagnation, # keept running and not move any allocation. # this test is more consistent on a fresh server with no history - start_server {tags {"defrag"} overrides {save ""}} { + start_server {tags {"defrag"} overrides {save "" server-threads 1}} { r flushdb r config resetstat r config set hz 100 From 1554161bdcc748d0d9473438937e57b1aed44d6d Mon Sep 17 00:00:00 2001 From: John Sully Date: Mon, 14 Jun 2021 22:06:36 +0000 Subject: [PATCH 27/41] Prevent test code crash due to no log data Former-commit-id: 0a56a73bd98d4e692ae77683fdb9dd644ecfc2eb --- tests/integration/psync2.tcl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integration/psync2.tcl b/tests/integration/psync2.tcl index 8459d2378..eccf6df2d 100644 --- a/tests/integration/psync2.tcl +++ b/tests/integration/psync2.tcl @@ -39,6 +39,7 @@ proc show_cluster_status {} { # all the lists are empty. # # regexp {^[0-9]+:[A-Z] [0-9]+ [A-z]+ [0-9]+ ([0-9:.]+) .*} $l - logdate + catch { while 1 { # Find the log with smallest time. set empty 0 @@ -67,6 +68,7 @@ proc show_cluster_status {} { puts "\[$best port $R_port($best)\] [lindex $log($best) 0]" set log($best) [lrange $log($best) 1 end] } + } } } From 88149ed9e733bd678ab13ef678e3b46f96615af1 Mon Sep 17 00:00:00 2001 From: christianEQ Date: Thu, 17 Jun 2021 18:32:11 +0000 Subject: [PATCH 28/41] accurate cpu usage in diagnostic tool Former-commit-id: 8ee7584cffc5c5cacfb7ad20fc964112974683e4 --- src/keydb-diagnostic-tool.cpp | 42 ++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/src/keydb-diagnostic-tool.cpp b/src/keydb-diagnostic-tool.cpp index 3dced8523..bc5778139 100644 --- a/src/keydb-diagnostic-tool.cpp +++ b/src/keydb-diagnostic-tool.cpp @@ -863,6 +863,27 @@ int extractPropertyFromInfo(const char *info, const char *key, double &val) { return 0; } +double getServerCpuTime(redisContext *ctx) { + redisReply *reply = (redisReply*)redisCommand(ctx, "INFO"); + if (reply->type != REDIS_REPLY_STRING) { + freeReplyObject(reply); + printf("Error executing INFO command. Exiting.\r\n"); + return -1; + } + + double used_cpu_user, used_cpu_sys; + if (extractPropertyFromInfo(reply->str, "used_cpu_user", used_cpu_user)) { + printf("Error reading user CPU usage from INFO command. Exiting.\r\n"); + return -1; + } + if (extractPropertyFromInfo(reply->str, "used_cpu_sys", used_cpu_sys)) { + printf("Error reading system CPU usage from INFO command. Exiting.\r\n"); + return -1; + } + freeReplyObject(reply); + return used_cpu_user + used_cpu_sys; +} + int main(int argc, const char **argv) { int i; @@ -898,11 +919,12 @@ int main(int argc, const char **argv) { const char *set_value = "abcdefghijklmnopqrstuvwxyz"; int threads_used = 0; + unsigned int period = 5; char command[63]; initBenchmarkThreads(); redisContext *ctx = getRedisContext(config.hostip, config.hostport, config.hostsocket); - double cpu_usage; + double cpu_usage, last_cpu_usage = getServerCpuTime(ctx); while (threads_used < config.max_threads) { printf("Creating %d clients for thread %d...\n", config.numclients, threads_used); @@ -920,20 +942,14 @@ int main(int argc, const char **argv) { } threads_used++; - sleep(1); - - redisReply *reply = (redisReply*)redisCommand(ctx, "INFO"); - if (reply->type != REDIS_REPLY_STRING) { - freeReplyObject(reply); - printf("Error executing INFO command. Exiting.\r\n"); + sleep(period); + + cpu_usage = getServerCpuTime(ctx); + if (cpu_usage < 0) { break; } - if (extractPropertyFromInfo(reply->str, "used_cpu_sys", cpu_usage)) { - printf("Error reading CPU usage from INFO command. Exiting.\r\n"); - break; - } - printf("CPU Usage: %f\r\n", cpu_usage); - freeReplyObject(reply); + printf("CPU Usage: %.1f%%\r\n", (cpu_usage - last_cpu_usage) * 100 / period); + last_cpu_usage = cpu_usage; } printf("Done.\n"); From ef3ba1699db28af0898baf935cc66d7018d92429 Mon Sep 17 00:00:00 2001 From: christianEQ Date: Thu, 17 Jun 2021 20:40:10 +0000 Subject: [PATCH 29/41] detect client cpu usage in diagnostic tool Former-commit-id: 693450393c848679b60c5a9bf55428ae1d4f769f --- src/keydb-diagnostic-tool.cpp | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/keydb-diagnostic-tool.cpp b/src/keydb-diagnostic-tool.cpp index bc5778139..176939bb8 100644 --- a/src/keydb-diagnostic-tool.cpp +++ b/src/keydb-diagnostic-tool.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -863,6 +864,13 @@ int extractPropertyFromInfo(const char *info, const char *key, double &val) { return 0; } +double getSelfCpuTime(struct rusage *self_ru) { + getrusage(RUSAGE_SELF, self_ru); + double user_time = self_ru->ru_utime.tv_sec + (self_ru->ru_utime.tv_usec / (double)1000000); + double system_time = self_ru->ru_stime.tv_sec + (self_ru->ru_stime.tv_usec / (double)1000000); + return user_time + system_time; +} + double getServerCpuTime(redisContext *ctx) { redisReply *reply = (redisReply*)redisCommand(ctx, "INFO"); if (reply->type != REDIS_REPLY_STRING) { @@ -924,7 +932,10 @@ int main(int argc, const char **argv) { initBenchmarkThreads(); redisContext *ctx = getRedisContext(config.hostip, config.hostport, config.hostsocket); - double cpu_usage, last_cpu_usage = getServerCpuTime(ctx); + double server_cpu_time, last_server_cpu_time = getServerCpuTime(ctx); + struct rusage self_ru; + double self_cpu_time, last_self_cpu_time = getSelfCpuTime(&self_ru); + while (threads_used < config.max_threads) { printf("Creating %d clients for thread %d...\n", config.numclients, threads_used); @@ -944,12 +955,16 @@ int main(int argc, const char **argv) { sleep(period); - cpu_usage = getServerCpuTime(ctx); - if (cpu_usage < 0) { + server_cpu_time = getServerCpuTime(ctx); + self_cpu_time = getSelfCpuTime(&self_ru); + if (server_cpu_time < 0) { break; } - printf("CPU Usage: %.1f%%\r\n", (cpu_usage - last_cpu_usage) * 100 / period); - last_cpu_usage = cpu_usage; + printf("CPU Usage Self: %.1f%%, Server: %.1f%%\r\n", + (self_cpu_time - last_self_cpu_time) * 100 / period, + (server_cpu_time - last_server_cpu_time) * 100 / period); + last_server_cpu_time = server_cpu_time; + last_self_cpu_time = self_cpu_time; } printf("Done.\n"); From e6900c37c12e1d772907d3ac321bad644a87b378 Mon Sep 17 00:00:00 2001 From: christianEQ Date: Fri, 18 Jun 2021 16:49:25 +0000 Subject: [PATCH 30/41] detect full load + server threads (diagnostic tool) Former-commit-id: fd4ed1e425e32b628c5850e83c9ea9411c471bbe --- src/keydb-diagnostic-tool.cpp | 70 ++++++++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 13 deletions(-) diff --git a/src/keydb-diagnostic-tool.cpp b/src/keydb-diagnostic-tool.cpp index 176939bb8..9c4d1299b 100644 --- a/src/keydb-diagnostic-tool.cpp +++ b/src/keydb-diagnostic-tool.cpp @@ -864,6 +864,16 @@ int extractPropertyFromInfo(const char *info, const char *key, double &val) { return 0; } +int extractPropertyFromInfo(const char *info, const char *key, unsigned int &val) { + char *line = strstr((char*)info, key); + if (line == nullptr) return 1; + line += strlen(key) + 1; // Skip past key name and following colon + char *newline = strchr(line, '\n'); + *newline = 0; // Terminate string after relevant line + val = atoi(line); + return 0; +} + double getSelfCpuTime(struct rusage *self_ru) { getrusage(RUSAGE_SELF, self_ru); double user_time = self_ru->ru_utime.tv_sec + (self_ru->ru_utime.tv_usec / (double)1000000); @@ -872,7 +882,7 @@ double getSelfCpuTime(struct rusage *self_ru) { } double getServerCpuTime(redisContext *ctx) { - redisReply *reply = (redisReply*)redisCommand(ctx, "INFO"); + redisReply *reply = (redisReply*)redisCommand(ctx, "INFO CPU"); if (reply->type != REDIS_REPLY_STRING) { freeReplyObject(reply); printf("Error executing INFO command. Exiting.\r\n"); @@ -892,6 +902,10 @@ double getServerCpuTime(redisContext *ctx) { return used_cpu_user + used_cpu_sys; } +bool isAtFullLoad(double cpuPercent, unsigned int threads) { + return cpuPercent / threads >= 96; +} + int main(int argc, const char **argv) { int i; @@ -926,7 +940,7 @@ int main(int argc, const char **argv) { } const char *set_value = "abcdefghijklmnopqrstuvwxyz"; - int threads_used = 0; + int self_threads = 0; unsigned int period = 5; char command[63]; @@ -935,36 +949,66 @@ int main(int argc, const char **argv) { double server_cpu_time, last_server_cpu_time = getServerCpuTime(ctx); struct rusage self_ru; double self_cpu_time, last_self_cpu_time = getSelfCpuTime(&self_ru); + double server_cpu_load, last_server_cpu_load, self_cpu_load, server_cpu_gain, last_server_cpu_gain; + + redisReply *reply = (redisReply*)redisCommand(ctx, "INFO CPU"); + if (reply->type != REDIS_REPLY_STRING) { + freeReplyObject(reply); + printf("Error executing INFO command. Exiting.\r\n"); + return 1; + } + unsigned int server_threads; + if (extractPropertyFromInfo(reply->str, "server_threads", server_threads)) { + printf("Error reading server threads from INFO command. Exiting.\r\n"); + return 1; + } + freeReplyObject(reply); + + printf("Server has %d threads.\n", server_threads); - while (threads_used < config.max_threads) { - printf("Creating %d clients for thread %d...\n", config.numclients, threads_used); + while (self_threads < config.max_threads) { + printf("Creating %d clients for thread %d...\n", config.numclients, self_threads); for (int i = 0; i < config.numclients; i++) { - sprintf(command, "SET %d %s\r\n", threads_used * config.numclients + i, set_value); - createClient(command, strlen(command), NULL,threads_used); + sprintf(command, "SET %d %s\r\n", self_threads * config.numclients + i, set_value); + createClient(command, strlen(command), NULL,self_threads); } - printf("Starting thread %d\n", threads_used); + printf("Starting thread %d\n", self_threads); - benchmarkThread *t = config.threads[threads_used]; + benchmarkThread *t = config.threads[self_threads]; if (pthread_create(&(t->thread), NULL, execBenchmarkThread, t)){ - fprintf(stderr, "FATAL: Failed to start thread %d.\n", threads_used); + fprintf(stderr, "FATAL: Failed to start thread %d.\n", self_threads); exit(1); } - threads_used++; + self_threads++; sleep(period); server_cpu_time = getServerCpuTime(ctx); self_cpu_time = getSelfCpuTime(&self_ru); + server_cpu_load = (server_cpu_time - last_server_cpu_time) * 100 / period; + self_cpu_load = (self_cpu_time - last_self_cpu_time) * 100 / period; if (server_cpu_time < 0) { break; } - printf("CPU Usage Self: %.1f%%, Server: %.1f%%\r\n", - (self_cpu_time - last_self_cpu_time) * 100 / period, - (server_cpu_time - last_server_cpu_time) * 100 / period); + printf("CPU Usage Self: %.1f%%, Server: %.1f%%\r\n", self_cpu_load, server_cpu_load); + server_cpu_gain = server_cpu_load - last_server_cpu_load; last_server_cpu_time = server_cpu_time; last_self_cpu_time = self_cpu_time; + last_server_cpu_load = server_cpu_load; + + + + if (isAtFullLoad(server_cpu_load, server_threads)) { + printf("Server is at full CPU load.\n"); + break; + } + + if (isAtFullLoad(self_cpu_load, self_threads)) { + printf("Diagnostic tool is at full CPU load.\n"); + break; + } } printf("Done.\n"); From 6ca00c68f4e632ed20ee3f8b05bd83092b45234f Mon Sep 17 00:00:00 2001 From: christianEQ Date: Fri, 18 Jun 2021 16:58:16 +0000 Subject: [PATCH 31/41] added config option for time to spin up new client threads (diagnostic tool) Former-commit-id: 3d0f729572b175457d4874b6e381754ac47e9055 --- src/keydb-diagnostic-tool.cpp | 65 +++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/src/keydb-diagnostic-tool.cpp b/src/keydb-diagnostic-tool.cpp index 9c4d1299b..b5986127a 100644 --- a/src/keydb-diagnostic-tool.cpp +++ b/src/keydb-diagnostic-tool.cpp @@ -76,6 +76,7 @@ static struct config { const char *hostsocket; int numclients; int liveclients; + int period_ms; int requests; int requests_issued; int requests_finished; @@ -662,6 +663,7 @@ void initConfigDefaults() { config.keepalive = 1; config.datasize = 3; config.pipeline = 1; + config.period_ms = 5000; config.showerrors = 0; config.randomkeys = 0; config.randomkeys_keyspacelen = 0; @@ -708,6 +710,9 @@ int parseOptions(int argc, const char **argv) { } else if (!strcmp(argv[i],"-k")) { if (lastarg) goto invalid; config.keepalive = atoi(argv[++i]); + } else if (!strcmp(argv[i],"--ms")) { + if (lastarg) goto invalid; + config.period_ms = atoi(argv[++i]); } else if (!strcmp(argv[i],"-h")) { if (lastarg) goto invalid; config.hostip = strdup(argv[++i]); @@ -805,36 +810,37 @@ invalid: usage: printf( "Usage: keydb-benchmark [-h ] [-p ] [-c ] [-n ] [-k ]\n\n" -" -h Server hostname (default 127.0.0.1)\n" -" -p Server port (default 6379)\n" -" -s Server socket (overrides host and port)\n" -" -a Password for Redis Auth\n" -" --user Used to send ACL style 'AUTH username pass'. Needs -a.\n" -" -c Number of parallel connections (default 50)\n" -" -n Total number of requests (default 100000)\n" -" -d Data size of SET/GET value in bytes (default 3)\n" -" --dbnum SELECT the specified db number (default 0)\n" -" --threads Enable multi-thread mode.\n" -" --cluster Enable cluster mode.\n" -" --enable-tracking Send CLIENT TRACKING on before starting benchmark.\n" -" -k 1=keep alive 0=reconnect (default 1)\n" -" -r Use random keys for SET/GET/INCR, random values for SADD,\n" -" random members and scores for ZADD.\n" +" -h Server hostname (default 127.0.0.1)\n" +" -p Server port (default 6379)\n" +" -s Server socket (overrides host and port)\n" +" --ms Time between spinning up new client threads\n" +" -a Password for Redis Auth\n" +" --user Used to send ACL style 'AUTH username pass'. Needs -a.\n" +" -c Number of parallel connections (default 50)\n" +" -n Total number of requests (default 100000)\n" +" -d Data size of SET/GET value in bytes (default 3)\n" +" --dbnum SELECT the specified db number (default 0)\n" +" --threads Enable multi-thread mode.\n" +" --cluster Enable cluster mode.\n" +" --enable-tracking Send CLIENT TRACKING on before starting benchmark.\n" +" -k 1=keep alive 0=reconnect (default 1)\n" +" -r Use random keys for SET/GET/INCR, random values for SADD,\n" +" random members and scores for ZADD.\n" " Using this option the benchmark will expand the string __rand_int__\n" " inside an argument with a 12 digits number in the specified range\n" " from 0 to keyspacelen-1. The substitution changes every time a command\n" " is executed. Default tests use this to hit random keys in the\n" " specified range.\n" -" -P Pipeline requests. Default 1 (no pipeline).\n" -" -e If server replies with errors, show them on stdout.\n" -" (no more than 1 error per second is displayed)\n" -" -q Quiet. Just show query/sec values\n" -" --precision Number of decimal places to display in latency output (default 0)\n" -" --csv Output in CSV format\n" -" -l Loop. Run the tests forever\n" -" -t Only run the comma separated list of tests. The test\n" -" names are the same as the ones produced as output.\n" -" -I Idle mode. Just open N idle connections and wait.\n\n" +" -P Pipeline requests. Default 1 (no pipeline).\n" +" -e If server replies with errors, show them on stdout.\n" +" (no more than 1 error per second is displayed)\n" +" -q Quiet. Just show query/sec values\n" +" --precision Number of decimal places to display in latency output (default 0)\n" +" --csv Output in CSV format\n" +" -l Loop. Run the tests forever\n" +" -t Only run the comma separated list of tests. The test\n" +" names are the same as the ones produced as output.\n" +" -I Idle mode. Just open N idle connections and wait.\n\n" "Examples:\n\n" " Run the benchmark with the default configuration against 127.0.0.1:6379:\n" " $ keydb-benchmark\n\n" @@ -941,7 +947,6 @@ int main(int argc, const char **argv) { const char *set_value = "abcdefghijklmnopqrstuvwxyz"; int self_threads = 0; - unsigned int period = 5; char command[63]; initBenchmarkThreads(); @@ -983,12 +988,12 @@ int main(int argc, const char **argv) { } self_threads++; - sleep(period); + usleep(config.period_ms * 1000); server_cpu_time = getServerCpuTime(ctx); self_cpu_time = getSelfCpuTime(&self_ru); - server_cpu_load = (server_cpu_time - last_server_cpu_time) * 100 / period; - self_cpu_load = (self_cpu_time - last_self_cpu_time) * 100 / period; + server_cpu_load = (server_cpu_time - last_server_cpu_time) * 100000 / config.period_ms; + self_cpu_load = (self_cpu_time - last_self_cpu_time) * 100000 / config.period_ms; if (server_cpu_time < 0) { break; } @@ -998,7 +1003,7 @@ int main(int argc, const char **argv) { last_self_cpu_time = self_cpu_time; last_server_cpu_load = server_cpu_load; - + if (isAtFullLoad(server_cpu_load, server_threads)) { printf("Server is at full CPU load.\n"); From 7d11673ba9bc8df51e13a2b3a875ba90c9894b3f Mon Sep 17 00:00:00 2001 From: christianEQ Date: Fri, 18 Jun 2021 19:01:51 +0000 Subject: [PATCH 32/41] detect stagnating server load before 100% (diagnostic tool) Former-commit-id: 534b70643b8f39303331048d3e86475caa08b864 --- src/keydb-diagnostic-tool.cpp | 57 ++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/src/keydb-diagnostic-tool.cpp b/src/keydb-diagnostic-tool.cpp index b5986127a..506cc3ebe 100644 --- a/src/keydb-diagnostic-tool.cpp +++ b/src/keydb-diagnostic-tool.cpp @@ -42,6 +42,7 @@ #include #include #include +#include extern "C" { #include /* Use hiredis sds. */ #include "hiredis.h" @@ -891,23 +892,31 @@ double getServerCpuTime(redisContext *ctx) { redisReply *reply = (redisReply*)redisCommand(ctx, "INFO CPU"); if (reply->type != REDIS_REPLY_STRING) { freeReplyObject(reply); - printf("Error executing INFO command. Exiting.\r\n"); + printf("Error executing INFO command. Exiting.\n"); return -1; } double used_cpu_user, used_cpu_sys; if (extractPropertyFromInfo(reply->str, "used_cpu_user", used_cpu_user)) { - printf("Error reading user CPU usage from INFO command. Exiting.\r\n"); + printf("Error reading user CPU usage from INFO command. Exiting.\n"); return -1; } if (extractPropertyFromInfo(reply->str, "used_cpu_sys", used_cpu_sys)) { - printf("Error reading system CPU usage from INFO command. Exiting.\r\n"); + printf("Error reading system CPU usage from INFO command. Exiting.\n"); return -1; } freeReplyObject(reply); return used_cpu_user + used_cpu_sys; } +double getMean(std::deque *q) { + double sum = 0; + for (long unsigned int i = 0; i < q->size(); i++) { + sum += (*q)[i]; + } + return sum / q->size(); +} + bool isAtFullLoad(double cpuPercent, unsigned int threads) { return cpuPercent / threads >= 96; } @@ -954,7 +963,9 @@ int main(int argc, const char **argv) { double server_cpu_time, last_server_cpu_time = getServerCpuTime(ctx); struct rusage self_ru; double self_cpu_time, last_self_cpu_time = getSelfCpuTime(&self_ru); - double server_cpu_load, last_server_cpu_load, self_cpu_load, server_cpu_gain, last_server_cpu_gain; + double server_cpu_load, last_server_cpu_load = 0, self_cpu_load, server_cpu_gain; + std::deque load_gain_history = {}; + double current_gain_avg, peak_gain_avg = 0; redisReply *reply = (redisReply*)redisCommand(ctx, "INFO CPU"); if (reply->type != REDIS_REPLY_STRING) { @@ -971,19 +982,15 @@ int main(int argc, const char **argv) { printf("Server has %d threads.\n", server_threads); - while (self_threads < config.max_threads) { - printf("Creating %d clients for thread %d...\n", config.numclients, self_threads); for (int i = 0; i < config.numclients; i++) { sprintf(command, "SET %d %s\r\n", self_threads * config.numclients + i, set_value); createClient(command, strlen(command), NULL,self_threads); } - printf("Starting thread %d\n", self_threads); - benchmarkThread *t = config.threads[self_threads]; if (pthread_create(&(t->thread), NULL, execBenchmarkThread, t)){ - fprintf(stderr, "FATAL: Failed to start thread %d.\n", self_threads); + fprintf(stderr, "FATAL: Failed to start thread %d. Exiting.\n", self_threads); exit(1); } self_threads++; @@ -997,23 +1004,42 @@ int main(int argc, const char **argv) { if (server_cpu_time < 0) { break; } - printf("CPU Usage Self: %.1f%%, Server: %.1f%%\r\n", self_cpu_load, server_cpu_load); + printf("%d threads, %d total clients. CPU Usage Self: %.1f%% (%.1f%% per thread), Server: %.1f%% (%.1f%% per thread)\r", + self_threads, + self_threads * config.numclients, + self_cpu_load, + self_cpu_load / self_threads, + server_cpu_load, + server_cpu_load / server_threads); + fflush(stdout); server_cpu_gain = server_cpu_load - last_server_cpu_load; + load_gain_history.push_back(server_cpu_gain); + if (load_gain_history.size() > 5) { + load_gain_history.pop_front(); + } + current_gain_avg = getMean(&load_gain_history); + if (current_gain_avg > peak_gain_avg) { + peak_gain_avg = current_gain_avg; + } last_server_cpu_time = server_cpu_time; last_self_cpu_time = self_cpu_time; last_server_cpu_load = server_cpu_load; - - if (isAtFullLoad(server_cpu_load, server_threads)) { - printf("Server is at full CPU load.\n"); + printf("\nServer is at full CPU load. If higher performance is expected, check server configuration.\n"); break; } - if (isAtFullLoad(self_cpu_load, self_threads)) { - printf("Diagnostic tool is at full CPU load.\n"); + if (current_gain_avg <= 0.05 * peak_gain_avg) { + printf("\nServer CPU load appears to have stagnated with increasing clients.\n" + "Server does not appear to be at full load. Check network for throughput.\n"); break; } + + if (self_threads * config.numclients > 2000) { + printf("\nClient limit of 2000 reached. Server is not at full load and appears to be increasing.\n" + "2000 clients should be more than enough to reach a bottleneck. Check all configuration.\n"); + } } printf("Done.\n"); @@ -1023,3 +1049,4 @@ int main(int argc, const char **argv) { return 0; } + \ No newline at end of file From 1db672e81a50ee897a4107a12bd27ea89e73f6d4 Mon Sep 17 00:00:00 2001 From: christianEQ Date: Fri, 18 Jun 2021 20:21:47 +0000 Subject: [PATCH 33/41] added starting line so diagnostic tool doesnt look frozen at first Former-commit-id: 2b5e5cfa4cf1478682c46d74436025c63ac6c217 --- src/keydb-diagnostic-tool.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/keydb-diagnostic-tool.cpp b/src/keydb-diagnostic-tool.cpp index 53a612b1b..4e39785c0 100644 --- a/src/keydb-diagnostic-tool.cpp +++ b/src/keydb-diagnostic-tool.cpp @@ -981,7 +981,8 @@ int main(int argc, const char **argv) { } freeReplyObject(reply); - printf("Server has %d threads.\n", server_threads); + printf("Server has %d threads.\nStarting...\n", server_threads); + fflush(stdout); while (self_threads < config.max_threads) { for (int i = 0; i < config.numclients; i++) { From 6b66b56c17489fcdac77c0a1f48fc496dda16784 Mon Sep 17 00:00:00 2001 From: christianEQ Date: Mon, 21 Jun 2021 18:36:21 +0000 Subject: [PATCH 34/41] -t threads option Former-commit-id: 0181b0e7a17ad5f83a544401110a9eac2b292aa4 --- src/keydb-diagnostic-tool.cpp | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/src/keydb-diagnostic-tool.cpp b/src/keydb-diagnostic-tool.cpp index 4e39785c0..d71cdb865 100644 --- a/src/keydb-diagnostic-tool.cpp +++ b/src/keydb-diagnostic-tool.cpp @@ -760,17 +760,6 @@ int parseOptions(int argc, const char **argv) { config.idlemode = 1; } else if (!strcmp(argv[i],"-e")) { config.showerrors = 1; - } else if (!strcmp(argv[i],"-t")) { - if (lastarg) goto invalid; - /* We get the list of tests to run as a string in the form - * get,set,lrange,...,test_N. Then we add a comma before and - * after the string in order to make sure that searching - * for ",testname," will always get a match if the test is - * enabled. */ - config.tests = sdsnew(","); - config.tests = sdscat(config.tests,(char*)argv[++i]); - config.tests = sdscat(config.tests,","); - sdstolower(config.tests); } else if (!strcmp(argv[i],"--dbnum")) { if (lastarg) goto invalid; config.dbnum = atoi(argv[++i]); @@ -780,14 +769,16 @@ int parseOptions(int argc, const char **argv) { config.precision = atoi(argv[++i]); if (config.precision < 0) config.precision = 0; if (config.precision > MAX_LATENCY_PRECISION) config.precision = MAX_LATENCY_PRECISION; - } else if (!strcmp(argv[i],"--threads")) { - if (lastarg) goto invalid; - config.max_threads = atoi(argv[++i]); - if (config.max_threads > MAX_THREADS) { - printf("WARNING: too many threads, limiting threads to %d.\n", - MAX_THREADS); + } else if (!strcmp(argv[i],"-t") || !strcmp(argv[i],"--threads")) { + if (lastarg) goto invalid; + config.max_threads = atoi(argv[++i]); + if (config.max_threads > MAX_THREADS) { + printf("WARNING: too many threads, limiting threads to %d.\n", MAX_THREADS); config.max_threads = MAX_THREADS; - } else if (config.max_threads < 0) config.max_threads = 0; + } else if (config.max_threads <= 0) { + printf("Warning: Invalid value for max threads. Defaulting to %d.\n", MAX_THREADS); + config.max_threads = MAX_THREADS; + } } else if (!strcmp(argv[i],"--cluster")) { config.cluster_mode = 1; } else if (!strcmp(argv[i],"--enable-tracking")) { From 45c4220b7f6eab4a281645fc910df2f610f8cfb1 Mon Sep 17 00:00:00 2001 From: christianEQ Date: Mon, 21 Jun 2021 18:40:37 +0000 Subject: [PATCH 35/41] added more verbose options Former-commit-id: fd2ac1fcfc94285cad683528f3d209b204ccfd2b --- src/keydb-diagnostic-tool.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/keydb-diagnostic-tool.cpp b/src/keydb-diagnostic-tool.cpp index d71cdb865..b782b6a91 100644 --- a/src/keydb-diagnostic-tool.cpp +++ b/src/keydb-diagnostic-tool.cpp @@ -703,22 +703,19 @@ int parseOptions(int argc, const char **argv) { for (i = 1; i < argc; i++) { lastarg = (i == (argc-1)); - if (!strcmp(argv[i],"-c")) { + if (!strcmp(argv[i],"-c") || !strcmp(argv[i],"--clients")) { if (lastarg) goto invalid; config.numclients = atoi(argv[++i]); - } else if (!strcmp(argv[i],"-n")) { - if (lastarg) goto invalid; - config.requests = atoi(argv[++i]); } else if (!strcmp(argv[i],"-k")) { if (lastarg) goto invalid; config.keepalive = atoi(argv[++i]); } else if (!strcmp(argv[i],"--ms")) { if (lastarg) goto invalid; config.period_ms = atoi(argv[++i]); - } else if (!strcmp(argv[i],"-h")) { + } else if (!strcmp(argv[i],"-h") || !strcmp(argv[i],"--host")) { if (lastarg) goto invalid; config.hostip = strdup(argv[++i]); - } else if (!strcmp(argv[i],"-p")) { + } else if (!strcmp(argv[i],"-p") || !strcmp(argv[i],"--port")) { if (lastarg) goto invalid; config.hostport = atoi(argv[++i]); } else if (!strcmp(argv[i],"-s")) { From 1b0a1f8fc7f6422aeafcf474f11ac960a6296ce3 Mon Sep 17 00:00:00 2001 From: christianEQ Date: Mon, 21 Jun 2021 19:57:14 +0000 Subject: [PATCH 36/41] changed --ms to --time Former-commit-id: d7cada0f38668f67eb307172a3e91599b1f00a24 --- src/keydb-diagnostic-tool.cpp | 36 ++++++++++------------------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/src/keydb-diagnostic-tool.cpp b/src/keydb-diagnostic-tool.cpp index b782b6a91..163311646 100644 --- a/src/keydb-diagnostic-tool.cpp +++ b/src/keydb-diagnostic-tool.cpp @@ -706,12 +706,13 @@ int parseOptions(int argc, const char **argv) { if (!strcmp(argv[i],"-c") || !strcmp(argv[i],"--clients")) { if (lastarg) goto invalid; config.numclients = atoi(argv[++i]); - } else if (!strcmp(argv[i],"-k")) { - if (lastarg) goto invalid; - config.keepalive = atoi(argv[++i]); - } else if (!strcmp(argv[i],"--ms")) { + } else if (!strcmp(argv[i],"--time")) { if (lastarg) goto invalid; config.period_ms = atoi(argv[++i]); + if (config.period_ms <= 0) { + printf("Warning: Invalid value for thread time. Defaulting to 5000ms.\n"); + config.period_ms = 5000; + } } else if (!strcmp(argv[i],"-h") || !strcmp(argv[i],"--host")) { if (lastarg) goto invalid; config.hostip = strdup(argv[++i]); @@ -770,7 +771,7 @@ int parseOptions(int argc, const char **argv) { if (lastarg) goto invalid; config.max_threads = atoi(argv[++i]); if (config.max_threads > MAX_THREADS) { - printf("WARNING: too many threads, limiting threads to %d.\n", MAX_THREADS); + printf("Warning: Too many threads, limiting threads to %d.\n", MAX_THREADS); config.max_threads = MAX_THREADS; } else if (config.max_threads <= 0) { printf("Warning: Invalid value for max threads. Defaulting to %d.\n", MAX_THREADS); @@ -800,17 +801,16 @@ invalid: usage: printf( "Usage: keydb-benchmark [-h ] [-p ] [-c ] [-n ] [-k ]\n\n" -" -h Server hostname (default 127.0.0.1)\n" -" -p Server port (default 6379)\n" +" -h, --host Server hostname (default 127.0.0.1)\n" +" -p, --port Server port (default 6379)\n" " -s Server socket (overrides host and port)\n" -" --ms Time between spinning up new client threads\n" +" --time