Added URI support to redis-benchmark (cli and benchmark share the same uri-parsing methods) (#9314)

- Add `-u <uri>` command line option to support `redis://` URI scheme.
- included server connection information object (`struct cliConnInfo`),
  used to describe an ip:port pair, db num user input, and user:pass to
  avoid a large number of function arguments.
- Using sds on connection info strings for redis-benchmark/redis-cli

Co-authored-by: yoav-steinberg <yoav@monfort.co.il>
This commit is contained in:
filipe oliveira 2021-09-14 17:45:06 +01:00 committed by GitHub
parent ea36d4de17
commit b5a879e1c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 280 additions and 247 deletions

View File

@ -38,12 +38,15 @@
#include <sdscompat.h> /* Use hiredis' sds compat header that maps sds calls to their hi_ variants */
#include <sds.h> /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#ifdef USE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <hiredis_ssl.h>
#endif
#define UNUSED(V) ((void) V)
/* Wrapper around redisSecureConnection to avoid hiredis_ssl dependencies if
* not building with TLS support.
@ -259,3 +262,111 @@ sds unquoteCString(char *str) {
return res;
}
/* URL-style percent decoding. */
#define isHexChar(c) (isdigit(c) || ((c) >= 'a' && (c) <= 'f'))
#define decodeHexChar(c) (isdigit(c) ? (c) - '0' : (c) - 'a' + 10)
#define decodeHex(h, l) ((decodeHexChar(h) << 4) + decodeHexChar(l))
static sds percentDecode(const char *pe, size_t len) {
const char *end = pe + len;
sds ret = sdsempty();
const char *curr = pe;
while (curr < end) {
if (*curr == '%') {
if ((end - curr) < 2) {
fprintf(stderr, "Incomplete URI encoding\n");
exit(1);
}
char h = tolower(*(++curr));
char l = tolower(*(++curr));
if (!isHexChar(h) || !isHexChar(l)) {
fprintf(stderr, "Illegal character in URI encoding\n");
exit(1);
}
char c = decodeHex(h, l);
ret = sdscatlen(ret, &c, 1);
curr++;
} else {
ret = sdscatlen(ret, curr++, 1);
}
}
return ret;
}
/* Parse a URI and extract the server connection information.
* URI scheme is based on the the provisional specification[1] excluding support
* for query parameters. Valid URIs are:
* scheme: "redis://"
* authority: [[<username> ":"] <password> "@"] [<hostname> [":" <port>]]
* path: ["/" [<db>]]
*
* [1]: https://www.iana.org/assignments/uri-schemes/prov/redis */
void parseRedisUri(const char *uri, const char* tool_name, cliConnInfo *connInfo, int *tls_flag) {
#ifdef USE_OPENSSL
UNUSED(tool_name);
#else
UNUSED(tls_flag);
#endif
const char *scheme = "redis://";
const char *tlsscheme = "rediss://";
const char *curr = uri;
const char *end = uri + strlen(uri);
const char *userinfo, *username, *port, *host, *path;
/* URI must start with a valid scheme. */
if (!strncasecmp(tlsscheme, curr, strlen(tlsscheme))) {
#ifdef USE_OPENSSL
*tls_flag = 1;
curr += strlen(tlsscheme);
#else
fprintf(stderr,"rediss:// is only supported when %s is compiled with OpenSSL\n", tool_name);
exit(1);
#endif
} else if (!strncasecmp(scheme, curr, strlen(scheme))) {
curr += strlen(scheme);
} else {
fprintf(stderr,"Invalid URI scheme\n");
exit(1);
}
if (curr == end) return;
/* Extract user info. */
if ((userinfo = strchr(curr,'@'))) {
if ((username = strchr(curr, ':')) && username < userinfo) {
connInfo->user = percentDecode(curr, username - curr);
curr = username + 1;
}
connInfo->auth = percentDecode(curr, userinfo - curr);
curr = userinfo + 1;
}
if (curr == end) return;
/* Extract host and port. */
path = strchr(curr, '/');
if (*curr != '/') {
host = path ? path - 1 : end;
if ((port = strchr(curr, ':'))) {
connInfo->hostport = atoi(port + 1);
host = port - 1;
}
connInfo->hostip = sdsnewlen(curr, host - curr + 1);
}
curr = path ? path + 1 : end;
if (curr == end) return;
/* Extract database number. */
connInfo->input_dbnum = atoi(curr);
}
void freeCliConnInfo(cliConnInfo connInfo){
if (connInfo.hostip) sdsfree(connInfo.hostip);
if (connInfo.auth) sdsfree(connInfo.auth);
if (connInfo.user) sdsfree(connInfo.user);
}

View File

@ -23,6 +23,16 @@ typedef struct cliSSLconfig {
char* ciphersuites;
} cliSSLconfig;
/* server connection information object, used to describe an ip:port pair, db num user input, and user:pass. */
typedef struct cliConnInfo {
char *hostip;
int hostport;
int input_dbnum;
char *auth;
char *user;
} cliConnInfo;
int cliSecureConnection(redisContext *c, cliSSLconfig config, const char **err);
ssize_t cliWriteConn(redisContext *c, const char *buf, size_t buf_len);
@ -35,4 +45,7 @@ sds *getSdsArrayFromArgv(int argc,char **argv, int quoted);
sds unquoteCString(char *str);
void parseRedisUri(const char *uri, const char* tool_name, cliConnInfo *connInfo, int *tls_flag);
void freeCliConnInfo(cliConnInfo connInfo);
#endif /* __CLICOMMON_H */

View File

@ -81,8 +81,7 @@ struct redisConfig;
static struct config {
aeEventLoop *el;
const char *hostip;
int hostport;
cliConnInfo conn_info;
const char *hostsocket;
int tls;
struct cliSSLconfig sslconfig;
@ -108,12 +107,9 @@ static struct config {
int csv;
int loop;
int idlemode;
int dbnum;
sds dbnumstr;
sds input_dbnumstr;
char *tests;
int stdinarg; /* get last arg from stdin. (-x option) */
char *auth;
const char *user;
int precision;
int num_threads;
struct benchmarkThread **threads;
@ -287,12 +283,12 @@ static redisContext *getRedisContext(const char *ip, int port,
goto cleanup;
}
}
if (config.auth == NULL)
if (config.conn_info.auth == NULL)
return ctx;
if (config.user == NULL)
reply = redisCommand(ctx,"AUTH %s", config.auth);
if (config.conn_info.user == NULL)
reply = redisCommand(ctx,"AUTH %s", config.conn_info.auth);
else
reply = redisCommand(ctx,"AUTH %s %s", config.user, config.auth);
reply = redisCommand(ctx,"AUTH %s %s", config.conn_info.user, config.conn_info.auth);
if (reply != NULL) {
if (reply->type == REDIS_REPLY_ERROR) {
if (hostsocket == NULL)
@ -677,8 +673,8 @@ static client createClient(char *cmd, size_t len, client from, int thread_id) {
c->cluster_node = NULL;
if (config.hostsocket == NULL || is_cluster_client) {
if (!is_cluster_client) {
ip = config.hostip;
port = config.hostport;
ip = config.conn_info.hostip;
port = config.conn_info.hostport;
} else {
int node_idx = 0;
if (config.num_threads < config.cluster_node_count)
@ -722,14 +718,14 @@ static client createClient(char *cmd, size_t len, client from, int thread_id) {
* 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) {
if (config.conn_info.auth) {
char *buf = NULL;
int len;
if (config.user == NULL)
len = redisFormatCommand(&buf, "AUTH %s", config.auth);
if (config.conn_info.user == NULL)
len = redisFormatCommand(&buf, "AUTH %s", config.conn_info.auth);
else
len = redisFormatCommand(&buf, "AUTH %s %s",
config.user, config.auth);
config.conn_info.user, config.conn_info.auth);
c->obuf = sdscatlen(c->obuf, buf, len);
free(buf);
c->prefix_pending++;
@ -747,9 +743,9 @@ static client createClient(char *cmd, size_t len, client from, int thread_id) {
* 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) {
if (config.conn_info.input_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);
(int)sdslen(config.input_dbnumstr),config.input_dbnumstr);
c->prefix_pending++;
}
c->prefixlen = sdslen(c->obuf);
@ -1082,9 +1078,9 @@ static void freeClusterNode(clusterNode *node) {
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
* config.conn_info.hostip and config.conn_info.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->ip && strcmp(node->ip, config.conn_info.hostip) != 0) sdsfree(node->ip);
if (node->redis_config != NULL) freeRedisConfig(node->redis_config);
zfree(node->slots);
zfree(node);
@ -1113,12 +1109,12 @@ static int fetchClusterConfiguration() {
int success = 1;
redisContext *ctx = NULL;
redisReply *reply = NULL;
ctx = getRedisContext(config.hostip, config.hostport, config.hostsocket);
ctx = getRedisContext(config.conn_info.hostip, config.conn_info.hostport, config.hostsocket);
if (ctx == NULL) {
exit(1);
}
clusterNode *firstNode = createClusterNode((char *) config.hostip,
config.hostport);
clusterNode *firstNode = createClusterNode((char *) config.conn_info.hostip,
config.conn_info.hostport);
if (!firstNode) {success = 0; goto cleanup;}
reply = redisCommand(ctx, "CLUSTER NODES");
success = (reply != NULL);
@ -1127,7 +1123,7 @@ static int fetchClusterConfiguration() {
if (!success) {
if (config.hostsocket == NULL) {
fprintf(stderr, "Cluster node %s:%d replied with error:\n%s\n",
config.hostip, config.hostport, reply->str);
config.conn_info.hostip, config.conn_info.hostport, reply->str);
} else {
fprintf(stderr, "Cluster node %s replied with error:\n%s\n",
config.hostsocket, reply->str);
@ -1425,10 +1421,10 @@ int parseOptions(int argc, char **argv) {
config.keepalive = atoi(argv[++i]);
} else if (!strcmp(argv[i],"-h")) {
if (lastarg) goto invalid;
config.hostip = strdup(argv[++i]);
config.conn_info.hostip = sdsnew(argv[++i]);
} else if (!strcmp(argv[i],"-p")) {
if (lastarg) goto invalid;
config.hostport = atoi(argv[++i]);
config.conn_info.hostport = atoi(argv[++i]);
} else if (!strcmp(argv[i],"-s")) {
if (lastarg) goto invalid;
config.hostsocket = strdup(argv[++i]);
@ -1436,10 +1432,13 @@ int parseOptions(int argc, char **argv) {
config.stdinarg = 1;
} else if (!strcmp(argv[i],"-a") ) {
if (lastarg) goto invalid;
config.auth = strdup(argv[++i]);
config.conn_info.auth = sdsnew(argv[++i]);
} else if (!strcmp(argv[i],"--user")) {
if (lastarg) goto invalid;
config.user = argv[++i];
config.conn_info.user = sdsnew(argv[++i]);
} else if (!strcmp(argv[i],"-u") && !lastarg) {
parseRedisUri(argv[++i],"redis-benchmark",&config.conn_info,&config.tls);
config.input_dbnumstr = sdsfromlonglong(config.conn_info.input_dbnum);
} else if (!strcmp(argv[i],"-d")) {
if (lastarg) goto invalid;
config.datasize = atoi(argv[++i]);
@ -1485,8 +1484,8 @@ int parseOptions(int argc, char **argv) {
sdstolower(config.tests);
} else if (!strcmp(argv[i],"--dbnum")) {
if (lastarg) goto invalid;
config.dbnum = atoi(argv[++i]);
config.dbnumstr = sdsfromlonglong(config.dbnum);
config.conn_info.input_dbnum = atoi(argv[++i]);
config.input_dbnumstr = sdsfromlonglong(config.conn_info.input_dbnum);
} else if (!strcmp(argv[i],"--precision")) {
if (lastarg) goto invalid;
config.precision = atoi(argv[++i]);
@ -1561,6 +1560,7 @@ usage:
" -s <socket> Server socket (overrides host and port)\n"
" -a <password> Password for Redis Auth\n"
" --user <username> Used to send ACL style 'AUTH username pass'. Needs -a.\n"
" -u <uri> Server URI.\n"
" -c <clients> Number of parallel connections (default 50)\n"
" -n <requests> Total number of requests (default 100000)\n"
" -d <size> Data size of SET/GET value in bytes (default 3)\n"
@ -1720,13 +1720,13 @@ int main(int argc, char **argv) {
config.loop = 0;
config.idlemode = 0;
config.clients = listCreate();
config.hostip = "127.0.0.1";
config.hostport = 6379;
config.conn_info.hostip = "127.0.0.1";
config.conn_info.hostport = 6379;
config.hostsocket = NULL;
config.tests = NULL;
config.dbnum = 0;
config.conn_info.input_dbnum = 0;
config.stdinarg = 0;
config.auth = NULL;
config.conn_info.auth = NULL;
config.precision = DEFAULT_LATENCY_PRECISION;
config.num_threads = 0;
config.threads = NULL;
@ -1759,7 +1759,7 @@ int main(int argc, char **argv) {
if (!fetchClusterConfiguration() || !config.cluster_nodes) {
if (!config.hostsocket) {
fprintf(stderr, "Failed to fetch cluster configuration from "
"%s:%d\n", config.hostip, config.hostport);
"%s:%d\n", config.conn_info.hostip, config.conn_info.hostport);
} else {
fprintf(stderr, "Failed to fetch cluster configuration from "
"%s\n", config.hostsocket);
@ -1795,7 +1795,7 @@ int main(int argc, char **argv) {
config.num_threads = config.cluster_node_count;
} else {
config.redis_config =
getRedisConfig(config.hostip, config.hostport, config.hostsocket);
getRedisConfig(config.conn_info.hostip, config.conn_info.hostport, config.hostsocket);
if (config.redis_config == NULL) {
fprintf(stderr, "WARNING: Could not fetch server CONFIG\n");
}
@ -2007,6 +2007,7 @@ int main(int argc, char **argv) {
} while(config.loop);
zfree(data);
freeCliConnInfo(config.conn_info);
if (config.redis_config != NULL) freeRedisConfig(config.redis_config);
return 0;

View File

@ -200,15 +200,13 @@ static void createClusterManagerCommand(char *cmdname, int argc, char **argv);
static redisContext *context;
static struct config {
char *hostip;
int hostport;
cliConnInfo conn_info;
char *hostsocket;
int tls;
cliSSLconfig sslconfig;
long repeat;
long interval;
int dbnum; /* db num currently selected */
int input_dbnum; /* db num user input */
int interactive;
int shutdown;
int monitor_mode;
@ -237,9 +235,7 @@ static struct config {
unsigned memkeys_samples;
int hotkeys;
int stdinarg; /* get last arg from stdin. (-x option) */
char *auth;
int askpass;
char *user;
int quoted_input; /* Force input args to be treated as quoted strings */
int output; /* output mode, see OUTPUT_* defines */
int push_output; /* Should we display spontaneous PUSH replies */
@ -306,7 +302,7 @@ static void cliRefreshPrompt(void) {
prompt = sdscatfmt(prompt,"redis %s",config.hostsocket);
} else {
char addr[256];
anetFormatAddr(addr, sizeof(addr), config.hostip, config.hostport);
anetFormatAddr(addr, sizeof(addr), config.conn_info.hostip, config.conn_info.hostport);
prompt = sdscatlen(prompt,addr,strlen(addr));
}
@ -355,102 +351,6 @@ static sds getDotfilePath(char *envoverride, char *dotfilename) {
return dotPath;
}
/* URL-style percent decoding. */
#define isHexChar(c) (isdigit(c) || (c >= 'a' && c <= 'f'))
#define decodeHexChar(c) (isdigit(c) ? c - '0' : c - 'a' + 10)
#define decodeHex(h, l) ((decodeHexChar(h) << 4) + decodeHexChar(l))
static sds percentDecode(const char *pe, size_t len) {
const char *end = pe + len;
sds ret = sdsempty();
const char *curr = pe;
while (curr < end) {
if (*curr == '%') {
if ((end - curr) < 2) {
fprintf(stderr, "Incomplete URI encoding\n");
exit(1);
}
char h = tolower(*(++curr));
char l = tolower(*(++curr));
if (!isHexChar(h) || !isHexChar(l)) {
fprintf(stderr, "Illegal character in URI encoding\n");
exit(1);
}
char c = decodeHex(h, l);
ret = sdscatlen(ret, &c, 1);
curr++;
} else {
ret = sdscatlen(ret, curr++, 1);
}
}
return ret;
}
/* Parse a URI and extract the server connection information.
* URI scheme is based on the the provisional specification[1] excluding support
* for query parameters. Valid URIs are:
* scheme: "redis://"
* authority: [[<username> ":"] <password> "@"] [<hostname> [":" <port>]]
* path: ["/" [<db>]]
*
* [1]: https://www.iana.org/assignments/uri-schemes/prov/redis */
static void parseRedisUri(const char *uri) {
const char *scheme = "redis://";
const char *tlsscheme = "rediss://";
const char *curr = uri;
const char *end = uri + strlen(uri);
const char *userinfo, *username, *port, *host, *path;
/* URI must start with a valid scheme. */
if (!strncasecmp(tlsscheme, curr, strlen(tlsscheme))) {
#ifdef USE_OPENSSL
config.tls = 1;
curr += strlen(tlsscheme);
#else
fprintf(stderr,"rediss:// is only supported when redis-cli is compiled with OpenSSL\n");
exit(1);
#endif
} else if (!strncasecmp(scheme, curr, strlen(scheme))) {
curr += strlen(scheme);
} else {
fprintf(stderr,"Invalid URI scheme\n");
exit(1);
}
if (curr == end) return;
/* Extract user info. */
if ((userinfo = strchr(curr,'@'))) {
if ((username = strchr(curr, ':')) && username < userinfo) {
config.user = percentDecode(curr, username - curr);
curr = username + 1;
}
config.auth = percentDecode(curr, userinfo - curr);
curr = userinfo + 1;
}
if (curr == end) return;
/* Extract host and port. */
path = strchr(curr, '/');
if (*curr != '/') {
host = path ? path - 1 : end;
if ((port = strchr(curr, ':'))) {
config.hostport = atoi(port + 1);
host = port - 1;
}
config.hostip = sdsnewlen(curr, host - curr + 1);
}
curr = path ? path + 1 : end;
if (curr == end) return;
/* Extract database number. */
config.input_dbnum = atoi(curr);
}
static uint64_t dictSdsHash(const void *key) {
return dictGenHashFunction((unsigned char*)key, sdslen((char*)key));
}
@ -802,9 +702,9 @@ static int cliAuth(redisContext *ctx, char *user, char *auth) {
/* Send SELECT input_dbnum to the server */
static int cliSelect(void) {
redisReply *reply;
if (config.input_dbnum == config.dbnum) return REDIS_OK;
if (config.conn_info.input_dbnum == config.dbnum) return REDIS_OK;
reply = redisCommand(context,"SELECT %d",config.input_dbnum);
reply = redisCommand(context,"SELECT %d",config.conn_info.input_dbnum);
if (reply == NULL) {
fprintf(stderr, "\nI/O error\n");
return REDIS_ERR;
@ -813,9 +713,9 @@ static int cliSelect(void) {
int result = REDIS_OK;
if (reply->type == REDIS_REPLY_ERROR) {
result = REDIS_ERR;
fprintf(stderr,"SELECT %d failed: %s\n",config.input_dbnum,reply->str);
fprintf(stderr,"SELECT %d failed: %s\n",config.conn_info.input_dbnum,reply->str);
} else {
config.dbnum = config.input_dbnum;
config.dbnum = config.conn_info.input_dbnum;
cliRefreshPrompt();
}
freeReplyObject(reply);
@ -858,7 +758,7 @@ static int cliConnect(int flags) {
/* 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);
context = redisConnect(config.conn_info.hostip,config.conn_info.hostport);
} else {
context = redisConnectUnix(config.hostsocket);
}
@ -880,7 +780,7 @@ static int cliConnect(int flags) {
(config.cluster_mode && config.cluster_reissue_command))
{
fprintf(stderr, "%s:%d: %s\n",
config.hostip,config.hostport,context->errstr);
config.conn_info.hostip,config.conn_info.hostport,context->errstr);
} else {
fprintf(stderr,"%s: %s\n",
config.hostsocket,context->errstr);
@ -899,7 +799,7 @@ static int cliConnect(int flags) {
anetKeepAlive(NULL, context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);
/* Do AUTH, select the right DB, switch to RESP3 if needed. */
if (cliAuth(context, config.user, config.auth) != REDIS_OK)
if (cliAuth(context, config.conn_info.user, config.conn_info.auth) != REDIS_OK)
return REDIS_ERR;
if (cliSelect() != REDIS_OK)
return REDIS_ERR;
@ -1337,12 +1237,12 @@ static int cliReadReply(int output_raw_strings) {
slot = atoi(s+1);
s = strrchr(p+1,':'); /* MOVED 3999[P]127.0.0.1[S]6381 */
*s = '\0';
sdsfree(config.hostip);
config.hostip = sdsnew(p+1);
config.hostport = atoi(s+1);
sdsfree(config.conn_info.hostip);
config.conn_info.hostip = sdsnew(p+1);
config.conn_info.hostport = atoi(s+1);
if (config.interactive)
printf("-> Redirected to slot [%d] located at %s:%d\n",
slot, config.hostip, config.hostport);
slot, config.conn_info.hostip, config.conn_info.hostport);
config.cluster_reissue_command = 1;
if (!strncmp(reply->str,"ASK ",4)) {
config.cluster_send_asking = 1;
@ -1486,7 +1386,7 @@ static int cliSendCommand(int argc, char **argv, long repeat) {
if (!strcasecmp(command,"select") && argc == 2 &&
config.last_cmd_type != REDIS_REPLY_ERROR)
{
config.input_dbnum = config.dbnum = atoi(argv[1]);
config.conn_info.input_dbnum = config.dbnum = atoi(argv[1]);
cliRefreshPrompt();
} else if (!strcasecmp(command,"auth") && (argc == 2 || argc == 3)) {
cliSelect();
@ -1501,20 +1401,20 @@ static int cliSendCommand(int argc, char **argv, long repeat) {
if (config.last_cmd_type == REDIS_REPLY_ERROR ||
config.last_cmd_type == REDIS_REPLY_NIL)
{
config.input_dbnum = config.dbnum = config.pre_multi_dbnum;
config.conn_info.input_dbnum = config.dbnum = config.pre_multi_dbnum;
}
cliRefreshPrompt();
} else if (!strcasecmp(command,"discard") && argc == 1 &&
config.last_cmd_type != REDIS_REPLY_ERROR)
{
config.in_multi = 0;
config.input_dbnum = config.dbnum = config.pre_multi_dbnum;
config.conn_info.input_dbnum = config.dbnum = config.pre_multi_dbnum;
cliRefreshPrompt();
} else if (!strcasecmp(command,"reset") && argc == 1 &&
config.last_cmd_type != REDIS_REPLY_ERROR) {
config.in_multi = 0;
config.dbnum = 0;
config.input_dbnum = 0;
config.conn_info.input_dbnum = 0;
config.resp3 = 0;
cliRefreshPrompt();
}
@ -1546,7 +1446,7 @@ static redisReply *reconnectingRedisCommand(redisContext *c, const char *fmt, ..
fflush(stdout);
redisFree(c);
c = redisConnect(config.hostip,config.hostport);
c = redisConnect(config.conn_info.hostip,config.conn_info.hostport);
if (!c->err && config.tls) {
const char *err = NULL;
if (cliSecureConnection(c, config.sslconfig, &err) == REDIS_ERR && err) {
@ -1584,8 +1484,8 @@ static int parseOptions(int argc, char **argv) {
int lastarg = i==argc-1;
if (!strcmp(argv[i],"-h") && !lastarg) {
sdsfree(config.hostip);
config.hostip = sdsnew(argv[++i]);
sdsfree(config.conn_info.hostip);
config.conn_info.hostip = sdsnew(argv[++i]);
} else if (!strcmp(argv[i],"-h") && lastarg) {
usage(0);
} else if (!strcmp(argv[i],"--help")) {
@ -1593,7 +1493,7 @@ static int parseOptions(int argc, char **argv) {
} else if (!strcmp(argv[i],"-x")) {
config.stdinarg = 1;
} else if (!strcmp(argv[i],"-p") && !lastarg) {
config.hostport = atoi(argv[++i]);
config.conn_info.hostport = atoi(argv[++i]);
} else if (!strcmp(argv[i],"-s") && !lastarg) {
config.hostsocket = argv[++i];
} else if (!strcmp(argv[i],"-r") && !lastarg) {
@ -1602,7 +1502,7 @@ static int parseOptions(int argc, char **argv) {
double seconds = atof(argv[++i]);
config.interval = seconds*1000000;
} else if (!strcmp(argv[i],"-n") && !lastarg) {
config.input_dbnum = atoi(argv[++i]);
config.conn_info.input_dbnum = atoi(argv[++i]);
} else if (!strcmp(argv[i], "--no-auth-warning")) {
config.no_auth_warning = 1;
} else if (!strcmp(argv[i], "--askpass")) {
@ -1610,11 +1510,11 @@ static int parseOptions(int argc, char **argv) {
} else if ((!strcmp(argv[i],"-a") || !strcmp(argv[i],"--pass"))
&& !lastarg)
{
config.auth = argv[++i];
config.conn_info.auth = sdsnew(argv[++i]);
} else if (!strcmp(argv[i],"--user") && !lastarg) {
config.user = argv[++i];
config.conn_info.user = sdsnew(argv[++i]);
} else if (!strcmp(argv[i],"-u") && !lastarg) {
parseRedisUri(argv[++i]);
parseRedisUri(argv[++i],"redis-cli",&config.conn_info,&config.tls);
} else if (!strcmp(argv[i],"--raw")) {
config.output = OUTPUT_RAW;
} else if (!strcmp(argv[i],"--no-raw")) {
@ -1852,7 +1752,7 @@ static int parseOptions(int argc, char **argv) {
exit(1);
}
if (!config.no_auth_warning && config.auth != NULL) {
if (!config.no_auth_warning && config.conn_info.auth != NULL) {
fputs("Warning: Using a password with '-a' or '-u' option on the command"
" line interface may not be safe.\n", stderr);
}
@ -1863,8 +1763,8 @@ static int parseOptions(int argc, char **argv) {
static void parseEnv() {
/* Set auth from env, but do not overwrite CLI arguments if passed */
char *auth = getenv(REDIS_CLI_AUTH_ENV);
if (auth != NULL && config.auth == NULL) {
config.auth = auth;
if (auth != NULL && config.conn_info.auth == NULL) {
config.conn_info.auth = auth;
}
char *cluster_yes = getenv(REDIS_CLI_CLUSTER_YES_ENV);
@ -2261,9 +2161,9 @@ static void repl(void) {
printf("Use 'restart' only in Lua debugging mode.");
}
} else if (argc == 3 && !strcasecmp(argv[0],"connect")) {
sdsfree(config.hostip);
config.hostip = sdsnew(argv[1]);
config.hostport = atoi(argv[2]);
sdsfree(config.conn_info.hostip);
config.conn_info.hostip = sdsnew(argv[1]);
config.conn_info.hostport = atoi(argv[2]);
cliRefreshPrompt();
cliConnect(CC_FORCE);
} else if (argc == 1 && !strcasecmp(argv[0],"clear")) {
@ -2850,13 +2750,13 @@ static int clusterManagerNodeConnect(clusterManagerNode *node) {
* commands. At the same time this improves the detection of real
* errors. */
anetKeepAlive(NULL, node->context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);
if (config.auth) {
if (config.conn_info.auth) {
redisReply *reply;
if (config.user == NULL)
reply = redisCommand(node->context,"AUTH %s", config.auth);
if (config.conn_info.user == NULL)
reply = redisCommand(node->context,"AUTH %s", config.conn_info.auth);
else
reply = redisCommand(node->context,"AUTH %s %s",
config.user,config.auth);
config.conn_info.user,config.conn_info.auth);
int ok = clusterManagerCheckRedisReply(node, reply, NULL);
if (reply != NULL) freeReplyObject(reply);
if (!ok) return 0;
@ -3691,8 +3591,8 @@ static redisReply *clusterManagerMigrateKeysInReply(clusterManagerNode *source,
char **argv = NULL;
size_t *argv_len = NULL;
int c = (replace ? 8 : 7);
if (config.auth) c += 2;
if (config.user) c += 1;
if (config.conn_info.auth) c += 2;
if (config.conn_info.user) c += 1;
size_t argc = c + reply->elements;
size_t i, offset = 6; // Keys Offset
argv = zcalloc(argc * sizeof(char *));
@ -3718,23 +3618,23 @@ static redisReply *clusterManagerMigrateKeysInReply(clusterManagerNode *source,
argv_len[offset] = 7;
offset++;
}
if (config.auth) {
if (config.user) {
if (config.conn_info.auth) {
if (config.conn_info.user) {
argv[offset] = "AUTH2";
argv_len[offset] = 5;
offset++;
argv[offset] = config.user;
argv_len[offset] = strlen(config.user);
argv[offset] = config.conn_info.user;
argv_len[offset] = strlen(config.conn_info.user);
offset++;
argv[offset] = config.auth;
argv_len[offset] = strlen(config.auth);
argv[offset] = config.conn_info.auth;
argv_len[offset] = strlen(config.conn_info.auth);
offset++;
} else {
argv[offset] = "AUTH";
argv_len[offset] = 4;
offset++;
argv[offset] = config.auth;
argv_len[offset] = strlen(config.auth);
argv[offset] = config.conn_info.auth;
argv_len[offset] = strlen(config.conn_info.auth);
offset++;
}
}
@ -6603,11 +6503,11 @@ static int clusterManagerCommandImport(int argc, char **argv) {
}
}
cmdfmt = sdsnew("MIGRATE %s %d %s %d %d");
if (config.auth) {
if (config.user) {
cmdfmt = sdscatfmt(cmdfmt," AUTH2 %s %s", config.user, config.auth);
if (config.conn_info.auth) {
if (config.conn_info.user) {
cmdfmt = sdscatfmt(cmdfmt," AUTH2 %s %s", config.conn_info.user, config.conn_info.auth);
} else {
cmdfmt = sdscatfmt(cmdfmt," AUTH %s", config.auth);
cmdfmt = sdscatfmt(cmdfmt," AUTH %s", config.conn_info.auth);
}
}
@ -8288,13 +8188,13 @@ int main(int argc, char **argv) {
struct timeval tv;
memset(&config.sslconfig, 0, sizeof(config.sslconfig));
config.hostip = sdsnew("127.0.0.1");
config.hostport = 6379;
config.conn_info.hostip = sdsnew("127.0.0.1");
config.conn_info.hostport = 6379;
config.hostsocket = NULL;
config.repeat = 1;
config.interval = 0;
config.dbnum = 0;
config.input_dbnum = 0;
config.conn_info.input_dbnum = 0;
config.interactive = 0;
config.shutdown = 0;
config.monitor_mode = 0;
@ -8319,9 +8219,9 @@ int main(int argc, char **argv) {
config.bigkeys = 0;
config.hotkeys = 0;
config.stdinarg = 0;
config.auth = NULL;
config.conn_info.auth = NULL;
config.askpass = 0;
config.user = NULL;
config.conn_info.user = NULL;
config.eval = NULL;
config.eval_ldb = 0;
config.eval_ldb_end = 0;
@ -8372,7 +8272,7 @@ int main(int argc, char **argv) {
parseEnv();
if (config.askpass) {
config.auth = askPassword("Please input password: ");
config.conn_info.auth = askPassword("Please input password: ");
}
if (config.cluster_manager_command.from_askpass) {

View File

@ -5,34 +5,54 @@ proc cmdstat {cmd} {
return [cmdrstat $cmd r]
}
# common code to reset stats, flush the db and run redis-benchmark
proc common_bench_setup {cmd} {
r config resetstat
r flushall
if {[catch { exec {*}$cmd } error]} {
set first_line [lindex [split $error "\n"] 0]
puts [colorstr red "redis-benchmark non zero code. first line: $first_line"]
fail "redis-benchmark non zero code. first line: $first_line"
}
}
# we use this extra asserts on a simple set,get test for features like uri parsing
# and other simple flag related tests
proc default_set_get_checks {} {
assert_match {*calls=10,*} [cmdstat set]
assert_match {*calls=10,*} [cmdstat get]
# assert one of the non benchmarked commands is not present
assert_match {} [cmdstat lrange]
}
start_server {tags {"benchmark network external:skip"}} {
start_server {} {
set master_host [srv 0 host]
set master_port [srv 0 port]
test {benchmark: set,get} {
r config resetstat
r flushall
set cmd [redisbenchmark $master_host $master_port "-c 5 -n 10 -t set,get"]
if {[catch { exec {*}$cmd } error]} {
set first_line [lindex [split $error "\n"] 0]
puts [colorstr red "redis-benchmark non zero code. first line: $first_line"]
fail "redis-benchmark non zero code. first line: $first_line"
}
assert_match {*calls=10,*} [cmdstat set]
assert_match {*calls=10,*} [cmdstat get]
# assert one of the non benchmarked commands is not present
assert_match {} [cmdstat lrange]
common_bench_setup $cmd
default_set_get_checks
}
test {benchmark: connecting using URI set,get} {
set cmd [redisbenchmarkuri $master_host $master_port "-c 5 -n 10 -t set,get"]
common_bench_setup $cmd
default_set_get_checks
}
test {benchmark: connecting using URI with authentication set,get} {
r config set masterauth pass
set cmd [redisbenchmarkuriuserpass $master_host $master_port "default" pass "-c 5 -n 10 -t set,get"]
common_bench_setup $cmd
default_set_get_checks
}
test {benchmark: full test suite} {
r config resetstat
set cmd [redisbenchmark $master_host $master_port "-c 10 -n 100"]
if {[catch { exec {*}$cmd } error]} {
set first_line [lindex [split $error "\n"] 0]
puts [colorstr red "redis-benchmark non zero code. first line: $first_line"]
fail "redis-benchmark non zero code. first line: $first_line"
}
common_bench_setup $cmd
# ping total calls are 2*issued commands per test due to PING_INLINE and PING_MBULK
assert_match {*calls=200,*} [cmdstat ping]
assert_match {*calls=100,*} [cmdstat set]
@ -55,32 +75,17 @@ start_server {tags {"benchmark network external:skip"}} {
}
test {benchmark: multi-thread set,get} {
r config resetstat
r flushall
set cmd [redisbenchmark $master_host $master_port "--threads 10 -c 5 -n 10 -t set,get"]
if {[catch { exec {*}$cmd } error]} {
set first_line [lindex [split $error "\n"] 0]
puts [colorstr red "redis-benchmark non zero code. first line: $first_line"]
fail "redis-benchmark non zero code. first line: $first_line"
}
assert_match {*calls=10,*} [cmdstat set]
assert_match {*calls=10,*} [cmdstat get]
# assert one of the non benchmarked commands is not present
assert_match {} [cmdstat lrange]
common_bench_setup $cmd
default_set_get_checks
# ensure only one key was populated
assert_match {1} [scan [regexp -inline {keys\=([\d]*)} [r info keyspace]] keys=%d]
}
test {benchmark: pipelined full set,get} {
r config resetstat
r flushall
set cmd [redisbenchmark $master_host $master_port "-P 5 -c 10 -n 10010 -t set,get"]
if {[catch { exec {*}$cmd } error]} {
set first_line [lindex [split $error "\n"] 0]
puts [colorstr red "redis-benchmark non zero code. first line: $first_line"]
fail "redis-benchmark non zero code. first line: $first_line"
}
common_bench_setup $cmd
assert_match {*calls=10010,*} [cmdstat set]
assert_match {*calls=10010,*} [cmdstat get]
# assert one of the non benchmarked commands is not present
@ -91,14 +96,8 @@ start_server {tags {"benchmark network external:skip"}} {
}
test {benchmark: arbitrary command} {
r config resetstat
r flushall
set cmd [redisbenchmark $master_host $master_port "-c 5 -n 150 INCRBYFLOAT mykey 10.0"]
if {[catch { exec {*}$cmd } error]} {
set first_line [lindex [split $error "\n"] 0]
puts [colorstr red "redis-benchmark non zero code. first line: $first_line"]
fail "redis-benchmark non zero code. first line: $first_line"
}
common_bench_setup $cmd
assert_match {*calls=150,*} [cmdstat incrbyfloat]
# assert one of the non benchmarked commands is not present
assert_match {} [cmdstat get]
@ -108,14 +107,8 @@ start_server {tags {"benchmark network external:skip"}} {
}
test {benchmark: keyspace length} {
r flushall
r config resetstat
set cmd [redisbenchmark $master_host $master_port "-r 50 -t set -n 1000"]
if {[catch { exec {*}$cmd } error]} {
set first_line [lindex [split $error "\n"] 0]
puts [colorstr red "redis-benchmark non zero code. first line: $first_line"]
fail "redis-benchmark non zero code. first line: $first_line"
}
common_bench_setup $cmd
assert_match {*calls=1000,*} [cmdstat set]
# assert one of the non benchmarked commands is not present
assert_match {} [cmdstat get]
@ -127,19 +120,20 @@ start_server {tags {"benchmark network external:skip"}} {
# tls specific tests
if {$::tls} {
test {benchmark: specific tls-ciphers} {
r flushall
r config resetstat
set cmd [redisbenchmark $master_host $master_port "-r 50 -t set -n 1000 --tls-ciphers \"DEFAULT:-AES128-SHA256\""]
if {[catch { exec {*}$cmd } error]} {
set first_line [lindex [split $error "\n"] 0]
puts [colorstr red "redis-benchmark non zero code. first line: $first_line"]
fail "redis-benchmark non zero code. first line: $first_line"
}
common_bench_setup $cmd
assert_match {*calls=1000,*} [cmdstat set]
# assert one of the non benchmarked commands is not present
assert_match {} [cmdstat get]
}
test {benchmark: tls connecting using URI with authentication set,get} {
r config set masterauth pass
set cmd [redisbenchmarkuriuserpass $master_host $master_port "default" pass "-c 5 -n 10 -t set,get"]
common_bench_setup $cmd
default_set_get_checks
}
test {benchmark: specific tls-ciphersuites} {
r flushall
r config resetstat

View File

@ -17,3 +17,17 @@ proc redisbenchmark {host port {opts {}}} {
lappend cmd {*}$opts
return $cmd
}
proc redisbenchmarkuri {host port {opts {}}} {
set cmd [list src/redis-benchmark -u redis://$host:$port]
lappend cmd {*}[redisbenchmark_tls_config "tests"]
lappend cmd {*}$opts
return $cmd
}
proc redisbenchmarkuriuserpass {host port user pass {opts {}}} {
set cmd [list src/redis-benchmark -u redis://$user:$pass@$host:$port]
lappend cmd {*}[redisbenchmark_tls_config "tests"]
lappend cmd {*}$opts
return $cmd
}