346 lines
11 KiB
C
346 lines
11 KiB
C
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <stdint.h>
|
||
#include <hiredis/hiredis.h>
|
||
|
||
#define TIME_SUB_MS(tv1, tv2) ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)
|
||
// #define PRINT printf
|
||
#define PRINT
|
||
|
||
static void die(redisContext *c, const char *msg) {
|
||
fprintf(stderr, "%s: %s\n", msg, c && c->err ? c->errstr : "unknown");
|
||
redisFree(c);
|
||
exit(1);
|
||
}
|
||
|
||
static int must_ok(redisReply *r, const char *what) {
|
||
if (!r) { fprintf(stderr, "%s: reply null\n", what); return -1; }
|
||
if (!(r->type == REDIS_REPLY_STATUS && r->str && strcasecmp(r->str, "OK") == 0)) {
|
||
fprintf(stderr, "%s: expect +OK, got type=%d str=%s\n",
|
||
what, r->type, r->str ? r->str : "(null)");
|
||
freeReplyObject(r);
|
||
return -1;
|
||
}
|
||
freeReplyObject(r);
|
||
}
|
||
|
||
static int must_int(redisReply *r, long long expect, const char *what) {
|
||
if (!r) { fprintf(stderr, "%s: reply null\n", what); return -1; }
|
||
if (r->type != REDIS_REPLY_INTEGER || r->integer != expect) {
|
||
fprintf(stderr, "%s: expect :%lld, got type=%d int=%lld\n",
|
||
what, expect, r->type, (long long)r->integer);
|
||
freeReplyObject(r);
|
||
return -1;
|
||
}
|
||
freeReplyObject(r);
|
||
}
|
||
|
||
static int must_bulk_eq(redisReply *r, const void *buf, size_t n, const char *what) {
|
||
if (!r) { fprintf(stderr, "%s: reply null\n", what); return -1; }
|
||
if (r->type != REDIS_REPLY_STRING || r->len != n || memcmp(r->str, buf, n) != 0) {
|
||
fprintf(stderr, "%s: bulk mismatch. type=%d len=%zu\n", what, r->type, r->len);
|
||
fprintf(stderr, "expect:%s, truely:%s\n", (const char*)buf, r->str);
|
||
freeReplyObject(r);
|
||
return -1;
|
||
}
|
||
freeReplyObject(r);
|
||
}
|
||
|
||
static int must_nil(redisReply *r, const char *what) {
|
||
if (!r) { fprintf(stderr, "%s: reply null\n", what); return -1; }
|
||
if (r->type != REDIS_REPLY_NIL) {
|
||
fprintf(stderr, "%s: expect nil, got type=%d\n", what, r->type);
|
||
freeReplyObject(r);
|
||
return -1;
|
||
}
|
||
freeReplyObject(r);
|
||
}
|
||
|
||
void basic_command_test(redisContext *c){
|
||
/* ---------- 1) 基本命令测试 ---------- */
|
||
const char *k = "k1";
|
||
const char *v1 = "v1";
|
||
const char *v2 = "v2";
|
||
|
||
must_ok((redisReply*)redisCommand(c, "SET %b %b", k, strlen(k), v1, strlen(v1)), "SET");
|
||
must_bulk_eq((redisReply*)redisCommand(c, "GET %b", k, strlen(k)), v1, strlen(v1), "GET");
|
||
must_int((redisReply*)redisCommand(c, "EXIST %b", k, strlen(k)), 1, "EXIST=1");
|
||
|
||
must_ok((redisReply*)redisCommand(c, "MOD %b %b", k, strlen(k), v2, strlen(v2)), "MOD");
|
||
must_bulk_eq((redisReply*)redisCommand(c, "GET %b", k, strlen(k)), v2, strlen(v2), "GET after MOD");
|
||
|
||
must_int((redisReply*)redisCommand(c, "DEL %b", k, strlen(k)), 1, "DEL=1");
|
||
must_int((redisReply*)redisCommand(c, "EXIST %b", k, strlen(k)), 0, "EXIST=0");
|
||
must_nil((redisReply*)redisCommand(c, "GET %b", k, strlen(k)), "GET nil");
|
||
|
||
printf("[OK] basic SET/GET/MOD/DEL/EXIST\n");
|
||
}
|
||
|
||
void special_char_test(redisContext *c){
|
||
/* ---------- 2) 特殊字符/二进制测试 ---------- */
|
||
uint8_t key2[] = { 'b','i','n',':',0x00,'K','\r','\n',0xFF };
|
||
uint8_t val2[] = { 0x00,'A','\r','\n','B',0x00,0xFF };
|
||
|
||
must_ok((redisReply*)redisCommand(c, "SET %b %b", key2, sizeof(key2), val2, sizeof(val2)),
|
||
"SET binary");
|
||
must_bulk_eq((redisReply*)redisCommand(c, "GET %b", key2, sizeof(key2)),
|
||
val2, sizeof(val2), "GET binary");
|
||
(void)redisCommand(c, "DEL %b", key2, sizeof(key2)); /* 不强制检查返回 */
|
||
|
||
printf("[OK] binary/special chars\n");
|
||
}
|
||
|
||
void save(redisContext *c){
|
||
must_ok((redisReply*)redisCommand(c, "SAVE"), "SAVE");
|
||
|
||
printf("[OK] SAVE\n");
|
||
}
|
||
|
||
void printmem(redisContext *c){
|
||
redisReply *r = (redisReply*)redisCommand(c, "MEMPRINT");
|
||
if (r == NULL) {
|
||
// 连接错误或命令发送失败
|
||
printf("redisCommand failed: %s\n", c->errstr);
|
||
return;
|
||
}
|
||
|
||
if (r->type == REDIS_REPLY_INTEGER) {
|
||
if (r->integer == 1) {
|
||
printf("MEMPRINT returned 1\n");
|
||
} else {
|
||
printf("MEMPRINT returned %lld\n", r->integer);
|
||
}
|
||
} else if (r->type == REDIS_REPLY_ERROR) {
|
||
printf("Redis error: %s\n", r->str);
|
||
} else {
|
||
printf("Unexpected reply type: %d\n", r->type);
|
||
}
|
||
|
||
printf("[OK] PRINT\n");
|
||
}
|
||
|
||
void pipline_set_test(redisContext *c, int start, int countN, const char *op, char* k, char *v){
|
||
/* ---------- 3) Pipeline 批处理测试 ---------- */
|
||
const int N = countN;
|
||
|
||
/* 一次塞 N 个 SET */
|
||
int end = start + N;
|
||
for (int i = start; i < end; i++) {
|
||
char kk[64], vv[64];
|
||
int kn = snprintf(kk, sizeof(kk), "%s:%d", k, i);
|
||
int vn = snprintf(vv, sizeof(vv), "%s:%d", v, i);
|
||
if (redisAppendCommand( c, "%s %b %b",
|
||
op,
|
||
kk, (size_t)kn,
|
||
vv, (size_t)vn) != REDIS_OK) {
|
||
die(c, "redisAppendCommand SET failed");
|
||
}
|
||
|
||
if(i%10000 == 0) PRINT("SEND: %d\n", i);
|
||
}
|
||
/* 再一次性把 N 个回复读出来 */
|
||
for (int i = start; i < end; i++) {
|
||
redisReply *r = NULL;
|
||
if (redisGetReply(c, (void**)&r) != REDIS_OK || !r) die(c, "redisGetReply SET failed");
|
||
must_ok(r, "pipeline SET reply");
|
||
|
||
if(i%10000 == 0) PRINT("RECV: %d\n", i);
|
||
}
|
||
|
||
PRINT("[OK] SET pipeline batch %d\n", N);
|
||
}
|
||
|
||
void pipline_get_test(redisContext *c, int start, int countN, const char *op, char* k, char *v){
|
||
const int N = countN;
|
||
|
||
/* pipeline GET + 校验 */
|
||
int end = start + N;
|
||
for (int i = start; i < end; i++) {
|
||
char kk[64];
|
||
int kn = snprintf(kk, sizeof(kk), "%s:%d", k, i);
|
||
if (redisAppendCommand( c, "%s %b",
|
||
op,
|
||
kk, (size_t)kn) != REDIS_OK) {
|
||
die(c, "redisAppendCommand GET failed");
|
||
}
|
||
|
||
if(i%10000 == 0) PRINT("SEND: %d\n", i);
|
||
}
|
||
for (int i = start; i < end; i++) {
|
||
redisReply *r = NULL;
|
||
if (redisGetReply(c, (void**)&r) != REDIS_OK || !r) die(c, "redisGetReply GET failed");
|
||
char expect[64];
|
||
int en = snprintf(expect, sizeof(expect), "%s:%d", v, i);
|
||
must_bulk_eq(r, expect, (size_t)en, "pipeline GET reply");
|
||
|
||
if(i%10000 == 0) PRINT("RECV: %d\n", i);
|
||
}
|
||
|
||
PRINT("[OK] GET pipeline batch %d\n", N);
|
||
}
|
||
|
||
void pipline_del_test(redisContext *c, int start, int countN, const char *op, char* k){
|
||
const int N = countN;
|
||
|
||
/* cleanup:pipeline DEL */
|
||
int end = start + N;
|
||
for (int i = start; i < end; i++) {
|
||
char kk[64];
|
||
int kn = snprintf(kk, sizeof(kk), "%s:%d", k, i);
|
||
if (redisAppendCommand( c, "%s %b",
|
||
op,
|
||
kk, (size_t)kn) != REDIS_OK) {
|
||
die(c, "redisAppendCommand DEL failed");
|
||
}
|
||
|
||
if(i%10000 == 0) PRINT("SEND: %d\n", i);
|
||
}
|
||
for (int i = start; i < end; i++) {
|
||
redisReply *r = NULL;
|
||
if (redisGetReply(c, (void**)&r) != REDIS_OK || !r) die(c, "redisGetReply DEL failed");
|
||
freeReplyObject(r); /* DEL 返回 int,这里不强制检查 */
|
||
|
||
if(i%10000 == 0) PRINT("RECV: %d\n", i);
|
||
}
|
||
|
||
PRINT("[OK] DEL pipeline batch %d\n", N);
|
||
}
|
||
|
||
long long test_nopersist_noreplica(redisContext *c, int rounds, long long batch_size){
|
||
struct timeval tv_begin, tv_end;
|
||
gettimeofday(&tv_begin, NULL);
|
||
|
||
long long total_ops = batch_size*rounds;
|
||
|
||
for(int i = 0;i < total_ops ; i += batch_size){
|
||
pipline_set_test(c, i, batch_size, "RSET", "p", "v");
|
||
}
|
||
|
||
for(int i = 0;i < total_ops ; i += batch_size){
|
||
pipline_get_test(c, i, batch_size, "RGET", "p", "v");
|
||
}
|
||
|
||
for(int i = 0;i < total_ops ; i += batch_size){
|
||
pipline_del_test(c, i, batch_size, "RDEL", "p");
|
||
}
|
||
|
||
gettimeofday(&tv_end, NULL);
|
||
int time_used = TIME_SUB_MS(tv_end, tv_begin);
|
||
long long qps = total_ops *3*1000/time_used;
|
||
printf("BATCH (N=%lld) --> time_used=%d ms, qps=%lld\n", total_ops *3, time_used, qps);
|
||
|
||
return qps;
|
||
}
|
||
|
||
long long test_memory(redisContext *c, int rounds, long long batch_size, int mod){
|
||
struct timeval tv_begin, tv_end;
|
||
gettimeofday(&tv_begin, NULL);
|
||
|
||
long long total_ops = batch_size*rounds;
|
||
|
||
if(mod == 1){
|
||
for(int i = 0;i < total_ops ; i += batch_size){
|
||
pipline_set_test(c, i, batch_size, "RSET","p1","v");
|
||
pipline_set_test(c, i, batch_size, "RSET","p2","v");
|
||
pipline_del_test(c, i, batch_size, "RDEL","p1");
|
||
}
|
||
}else if(mod == 2){
|
||
for(int i = 0;i < total_ops ; i += batch_size){
|
||
pipline_set_test(c, i, batch_size, "RSET","p3","v");
|
||
pipline_del_test(c, i, batch_size, "RDEL","p2");
|
||
pipline_del_test(c, i, batch_size, "RDEL","p3");
|
||
}
|
||
}
|
||
|
||
|
||
gettimeofday(&tv_end, NULL);
|
||
int time_used = TIME_SUB_MS(tv_end, tv_begin);
|
||
long long qps = total_ops *6*1000/time_used;
|
||
printf("BATCH (N=%lld) --> time_used=%d ms, qps=%lld\n", total_ops *3, time_used, qps);
|
||
|
||
return qps;
|
||
}
|
||
|
||
int main(int argc, char **argv) {
|
||
if(argc < 4) {
|
||
printf("invalid input\n");
|
||
return -1;
|
||
}
|
||
|
||
const char *host = argv[1];
|
||
int port = atoi(argv[2]);
|
||
int mode = atoi(argv[3]);
|
||
|
||
redisContext *c = redisConnect(host, port);
|
||
if (!c || c->err) die(c, "connect failed");
|
||
|
||
redisReply *reply = redisCommand(c, "MEMPRINT");
|
||
|
||
printf("Connected to %s:%d, %lld\n", host, port, reply->integer);
|
||
|
||
if(mode == 0){
|
||
save(c);
|
||
}else if(mode == 1){
|
||
printmem(c);
|
||
}else if(mode == 2){
|
||
basic_command_test(c);
|
||
}else if(mode == 3){
|
||
int rounds = 10;
|
||
long long batch_size = 100000;
|
||
int testrounds = 50;
|
||
long long total_qps = 0;
|
||
|
||
for(int i = 0;i < testrounds; ++ i){
|
||
total_qps += test_nopersist_noreplica(c, rounds, batch_size);
|
||
}
|
||
printf("average qps:%lld\n", total_qps/testrounds);
|
||
}else if(mode == 4){
|
||
int rounds = 10;
|
||
long long batch_size = 100000;
|
||
int testrounds = 5;
|
||
long long total_qps = 0;
|
||
|
||
for(int i = 0;i < testrounds; ++ i){
|
||
total_qps += test_nopersist_noreplica(c, rounds, batch_size);
|
||
}
|
||
printf("average qps:%lld\n", total_qps/testrounds);
|
||
|
||
}else if(mode == 51){
|
||
int rounds = 30;
|
||
long long batch_size = 100000;
|
||
test_memory(c, rounds, batch_size, 1);
|
||
|
||
|
||
}else if(mode == 52){
|
||
int rounds = 30;
|
||
long long batch_size = 100000;
|
||
test_memory(c, rounds, batch_size, 2);
|
||
|
||
}else if(mode == 10){
|
||
pipline_set_test(c, 0, 1000, "SET","p","v");
|
||
}else if(mode == 11){
|
||
pipline_get_test(c, 0, 1000, "GET","p", "v");
|
||
}else if(mode == 12){
|
||
pipline_del_test(c, 0, 1000, "DEL","p");
|
||
|
||
}else if(mode == 20){
|
||
pipline_set_test(c, 0, 1000, "RSET","p","v");
|
||
}else if(mode == 21){
|
||
pipline_get_test(c, 0, 1000, "RGET","p", "v");
|
||
}else if(mode == 22){
|
||
pipline_del_test(c, 0, 1000, "RDEL","p");
|
||
|
||
}else if(mode == 30){
|
||
pipline_set_test(c, 0, 1000, "HSET","p","v");
|
||
}else if(mode == 31){
|
||
pipline_get_test(c, 0, 1000, "HGET","p", "v");
|
||
}else if(mode == 32){
|
||
pipline_del_test(c, 0, 1000, "HDEL","p");
|
||
}
|
||
|
||
redisFree(c);
|
||
printf("ALL TESTS PASSED.\n");
|
||
return 0;
|
||
}
|