From 2e8c7b6fe2a84fb794c7f36cccf0b47531d4c0a0 Mon Sep 17 00:00:00 2001 From: Yao Yue Date: Sat, 19 May 2018 16:12:13 -0700 Subject: [PATCH] adding redis binary (barebone) (#166) --- CMakeLists.txt | 1 + src/server/CMakeLists.txt | 4 + src/server/redis/CMakeLists.txt | 30 ++++ src/server/redis/admin/CMakeLists.txt | 4 + src/server/redis/admin/process.c | 80 ++++++++++ src/server/redis/admin/process.h | 4 + src/server/redis/data/CMakeLists.txt | 5 + src/server/redis/data/cmd_misc.c | 30 ++++ src/server/redis/data/cmd_misc.h | 13 ++ src/server/redis/data/process.c | 187 ++++++++++++++++++++++++ src/server/redis/data/process.h | 39 +++++ src/server/redis/main.c | 203 ++++++++++++++++++++++++++ src/server/redis/setting.c | 20 +++ src/server/redis/setting.h | 50 +++++++ src/server/redis/stats.c | 22 +++ src/server/redis/stats.h | 41 ++++++ src/server/slimredis/CMakeLists.txt | 1 - 17 files changed, 733 insertions(+), 1 deletion(-) create mode 100644 src/server/redis/CMakeLists.txt create mode 100644 src/server/redis/admin/CMakeLists.txt create mode 100644 src/server/redis/admin/process.c create mode 100644 src/server/redis/admin/process.h create mode 100644 src/server/redis/data/CMakeLists.txt create mode 100644 src/server/redis/data/cmd_misc.c create mode 100644 src/server/redis/data/cmd_misc.h create mode 100644 src/server/redis/data/process.c create mode 100644 src/server/redis/data/process.h create mode 100644 src/server/redis/main.c create mode 100644 src/server/redis/setting.c create mode 100644 src/server/redis/setting.h create mode 100644 src/server/redis/stats.c create mode 100644 src/server/redis/stats.h diff --git a/CMakeLists.txt b/CMakeLists.txt index bad38a862..648bfc9b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,7 @@ option(HAVE_LOGGING "logging enabled by default" ON) option(HAVE_STATS "stats enabled by default" ON) option(TARGET_PINGSERVER "build pingserver binary" ON) +option(TARGET_REDIS "build redis binary" ON) option(TARGET_SLIMREDIS "build slimredis binary" ON) option(TARGET_SLIMCACHE "build slimcache binary" ON) option(TARGET_TWEMCACHE "build twemcache binary" ON) diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt index 4fffed1f3..fd7805623 100644 --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -2,6 +2,10 @@ if(TARGET_PINGSERVER) add_subdirectory(pingserver) endif() +if(TARGET_REDIS) + add_subdirectory(redis) +endif() + if(TARGET_SLIMREDIS) add_subdirectory(slimredis) endif() diff --git a/src/server/redis/CMakeLists.txt b/src/server/redis/CMakeLists.txt new file mode 100644 index 000000000..e591bb765 --- /dev/null +++ b/src/server/redis/CMakeLists.txt @@ -0,0 +1,30 @@ +add_subdirectory(admin) +add_subdirectory(data) + +set(SOURCE + ${SOURCE} + main.c + setting.c + stats.c) + +set(MODULES + core + ds_bitmap + protocol_admin + protocol_redis + slab + time + util) + +set(LIBS + ccommon-static + ${CMAKE_THREAD_LIBS_INIT}) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/_bin) +set(TARGET_NAME ${PROJECT_NAME}_redis) + +add_executable(${TARGET_NAME} ${SOURCE}) +target_link_libraries(${TARGET_NAME} ${MODULES} ${LIBS}) + +install(TARGETS ${TARGET_NAME} RUNTIME DESTINATION bin) +add_dependencies(service ${TARGET_NAME}) diff --git a/src/server/redis/admin/CMakeLists.txt b/src/server/redis/admin/CMakeLists.txt new file mode 100644 index 000000000..31a7e65f3 --- /dev/null +++ b/src/server/redis/admin/CMakeLists.txt @@ -0,0 +1,4 @@ +set(SOURCE + ${SOURCE} + ${CMAKE_CURRENT_SOURCE_DIR}/process.c + PARENT_SCOPE) diff --git a/src/server/redis/admin/process.c b/src/server/redis/admin/process.c new file mode 100644 index 000000000..5927e85a9 --- /dev/null +++ b/src/server/redis/admin/process.c @@ -0,0 +1,80 @@ +#include "process.h" + +#include "protocol/admin/admin_include.h" +#include "util/procinfo.h" + +#include +#include + +#define REDIS_ADMIN_MODULE_NAME "redis::admin" + +extern struct stats stats; +extern unsigned int nmetric; + +static bool admin_init = false; +static char *buf = NULL; +static size_t cap; + +void +admin_process_setup(void) +{ + log_info("set up the %s module", REDIS_ADMIN_MODULE_NAME); + if (admin_init) { + log_warn("%s has already been setup, overwrite", + REDIS_ADMIN_MODULE_NAME); + } + + cap = nmetric * METRIC_PRINT_LEN; + buf = cc_alloc(cap); + /* TODO: check return status of cc_alloc */ + + admin_init = true; +} + +void +admin_process_teardown(void) +{ + log_info("tear down the %s module", REDIS_ADMIN_MODULE_NAME); + if (!admin_init) { + log_warn("%s has never been setup", REDIS_ADMIN_MODULE_NAME); + } + + admin_init = false; +} + +static void +_admin_stats_default(struct response *rsp, struct request *req) +{ + procinfo_update(); + rsp->data.data = buf; + rsp->data.len = print_stats(buf, cap, (struct metric *)&stats, nmetric); +} + +static void +_admin_stats(struct response *rsp, struct request *req) +{ + if (bstring_empty(&req->arg)) { + _admin_stats_default(rsp, req); + return; + } else { + rsp->type = RSP_INVALID; + } +} + +void +admin_process_request(struct response *rsp, struct request *req) +{ + rsp->type = RSP_GENERIC; + + switch (req->type) { + case REQ_STATS: + _admin_stats(rsp, req); + break; + case REQ_VERSION: + rsp->data = str2bstr(VERSION_PRINTED); + break; + default: + rsp->type = RSP_INVALID; + break; + } +} diff --git a/src/server/redis/admin/process.h b/src/server/redis/admin/process.h new file mode 100644 index 000000000..361230004 --- /dev/null +++ b/src/server/redis/admin/process.h @@ -0,0 +1,4 @@ +#pragma once + +void admin_process_setup(void); +void admin_process_teardown(void); diff --git a/src/server/redis/data/CMakeLists.txt b/src/server/redis/data/CMakeLists.txt new file mode 100644 index 000000000..71e5e1565 --- /dev/null +++ b/src/server/redis/data/CMakeLists.txt @@ -0,0 +1,5 @@ +set(SOURCE + ${SOURCE} + ${CMAKE_CURRENT_SOURCE_DIR}/process.c + ${CMAKE_CURRENT_SOURCE_DIR}/cmd_misc.c + PARENT_SCOPE) diff --git a/src/server/redis/data/cmd_misc.c b/src/server/redis/data/cmd_misc.c new file mode 100644 index 000000000..3c67072ea --- /dev/null +++ b/src/server/redis/data/cmd_misc.c @@ -0,0 +1,30 @@ +#include "process.h" + +#include "protocol/data/redis_include.h" +#include +#include + + +bool allow_flush = ALLOW_FLUSH; + +void +cmd_ping(struct response *rsp, struct request *req, struct command *cmd) +{ + struct element *el = NULL; + + el = array_push(rsp->token); + ASSERT(el != NULL); /* cannot fail because we preallocate tokens */ + + if (cmd->nopt == 0) { /* no additional argument, respond pong */ + rsp->type = ELEM_STR; + el->type = ELEM_STR; + el->bstr = str2bstr(RSP_PONG); + } else { /* behave as echo, use bulk string */ + struct element *arg = (struct element *)array_get(req->token, 1); + rsp->type = ELEM_BULK; + el->type = ELEM_BULK; + el->bstr = arg->bstr; + } + + INCR(process_metrics, ping); +} diff --git a/src/server/redis/data/cmd_misc.h b/src/server/redis/data/cmd_misc.h new file mode 100644 index 000000000..ca2410717 --- /dev/null +++ b/src/server/redis/data/cmd_misc.h @@ -0,0 +1,13 @@ +#pragma once + +/* name type description */ +#define PROCESS_MISC_METRIC(ACTION) \ + ACTION( flushall, METRIC_COUNTER, "# flushall requests" )\ + ACTION( ping, METRIC_COUNTER, "# ping requests" ) + +struct request; +struct response; +struct command; + +/* cmd_* functions must be command_fn (process.c) compatible */ +void cmd_ping(struct response *rsp, struct request *req, struct command *cmd); diff --git a/src/server/redis/data/process.c b/src/server/redis/data/process.c new file mode 100644 index 000000000..a9f399f4b --- /dev/null +++ b/src/server/redis/data/process.c @@ -0,0 +1,187 @@ +#include "process.h" + +#include "protocol/data/redis_include.h" + +#include +#include +#include + +#define REDIS_PROCESS_MODULE_NAME "redis::process" + +#define OVERSIZE_ERR_MSG "oversized value, cannot be stored" +#define OOM_ERR_MSG "server is out of memory" +#define CMD_ERR_MSG "command not supported" +#define OTHER_ERR_MSG "unknown server error" + + +typedef void (* command_fn)(struct response *, struct request *, struct command *cmd); +static command_fn command_registry[REQ_SENTINEL]; + +static bool process_init = false; +process_metrics_st *process_metrics = NULL; + +void +process_setup(process_options_st *options, process_metrics_st *metrics) +{ + log_info("set up the %s module", REDIS_PROCESS_MODULE_NAME); + + if (process_init) { + log_warn("%s has already been setup, overwrite", + REDIS_PROCESS_MODULE_NAME); + } + + process_metrics = metrics; + + if (options != NULL) { + allow_flush = option_bool(&options->allow_flush); + } + + command_registry[REQ_PING] = cmd_ping; + + process_init = true; +} + +void +process_teardown(void) +{ + log_info("tear down the %s module", REDIS_PROCESS_MODULE_NAME); + if (!process_init) { + log_warn("%s has never been setup", REDIS_PROCESS_MODULE_NAME); + } + + command_registry[REQ_PING] = cmd_ping; + + allow_flush = ALLOW_FLUSH; + process_metrics = NULL; + process_init = false; +} + + +void +process_request(struct response *rsp, struct request *req) +{ + struct command cmd; + command_fn func = command_registry[req->type]; + + if (func == NULL) { + struct element *reply = (struct element *)array_push(rsp->token); + log_warn("command is recognized but not implemented"); + + rsp->type = reply->type = ELEM_ERR; + reply->bstr = str2bstr(RSP_ERR_NOSUPPORT); + INCR(process_metrics, process_ex); + + return; + } + + cmd = command_table[req->type]; + cmd.nopt = req->token->nelem - cmd.narg; + + log_verb("processing command '%.*s' with %d optional arguments", + cmd.bstr.len, cmd.bstr.data, cmd.nopt); + func(rsp, req, &cmd); +} + +int +redis_process_read(struct buf **rbuf, struct buf **wbuf, void **data) +{ + parse_rstatus_t status; + struct request *req; /* data should be NULL or hold a req pointer */ + struct response *rsp; + + req = request_borrow(); + rsp = response_borrow(); + if (req == NULL || rsp == NULL) { + goto error; + } + + /* keep parse-process-compose until running out of data in rbuf */ + while (buf_rsize(*rbuf) > 0) { + request_reset(req); + response_reset(rsp); + + /* stage 1: parsing */ + log_verb("%"PRIu32" bytes left", buf_rsize(*rbuf)); + + status = parse_req(req, *rbuf); + if (status == PARSE_EUNFIN) { + buf_lshift(*rbuf); + goto done; + } + if (status != PARSE_OK) { + /* parsing errors are all client errors, since we don't know + * how to recover from client errors in this condition (we do not + * have a valid request so we don't know where the invalid request + * ends), we should close the connection + */ + log_warn("illegal request received, status: %d", status); + INCR(process_metrics, process_ex); + INCR(process_metrics, process_client_ex); + goto error; + } + + /* stage 2: processing- check for quit, allocate response(s), process */ + + /* quit is special, no response expected */ + if (req->type == REQ_QUIT) { + log_info("peer called quit"); + goto error; + } + + /* actual processing */ + process_request(rsp, req); + + /* stage 3: write response(s) if necessary */ + + /* noreply means no need to write to buffers */ + if (compose_rsp(wbuf, rsp) < 0) { + log_error("composing rsp erred"); + INCR(process_metrics, process_ex); + INCR(process_metrics, process_server_ex); + goto error; + } + + /* logging, clean-up */ + } + +done: + request_return(&req); + response_return(&rsp); + + return 0; + +error: + request_return(&req); + response_return(&rsp); + + return -1; +} + + +int +redis_process_write(struct buf **rbuf, struct buf **wbuf, void **data) +{ + log_verb("post-write processing"); + + buf_lshift(*rbuf); + dbuf_shrink(rbuf); + buf_lshift(*wbuf); + dbuf_shrink(wbuf); + + return 0; +} + + +int +redis_process_error(struct buf **rbuf, struct buf **wbuf, void **data) +{ + log_verb("post-error processing"); + + /* normalize buffer size */ + buf_reset(*rbuf); + dbuf_shrink(rbuf); + buf_reset(*wbuf); + dbuf_shrink(wbuf); + + return 0; +} diff --git a/src/server/redis/data/process.h b/src/server/redis/data/process.h new file mode 100644 index 000000000..e5be43dae --- /dev/null +++ b/src/server/redis/data/process.h @@ -0,0 +1,39 @@ +#pragma once + +#include "cmd_misc.h" + +#include +#include +#include + +#define ALLOW_FLUSH false + +/* name type default description */ +#define PROCESS_OPTION(ACTION) \ + ACTION( allow_flush, OPTION_TYPE_BOOL, ALLOW_FLUSH, "allow flushing on the data port" ) + +typedef struct { + PROCESS_OPTION(OPTION_DECLARE) +} process_options_st; + +/* name type description */ +#define PROCESS_METRIC(ACTION) \ + ACTION( process_req, METRIC_COUNTER, "# requests processed" )\ + ACTION( process_ex, METRIC_COUNTER, "# processing error" )\ + ACTION( process_client_ex, METRIC_COUNTER, "# internal error" )\ + ACTION( process_server_ex, METRIC_COUNTER, "# internal error" ) + +typedef struct { + PROCESS_METRIC(METRIC_DECLARE) + PROCESS_MISC_METRIC(METRIC_DECLARE) +} process_metrics_st; + +extern process_metrics_st *process_metrics; +extern bool allow_flush; + +void process_setup(process_options_st *options, process_metrics_st *metrics); +void process_teardown(void); + +int redis_process_read(struct buf **rbuf, struct buf **wbuf, void **data); +int redis_process_write(struct buf **rbuf, struct buf **wbuf, void **data); +int redis_process_error(struct buf **rbuf, struct buf **wbuf, void **data); diff --git a/src/server/redis/main.c b/src/server/redis/main.c new file mode 100644 index 000000000..b0a9550ef --- /dev/null +++ b/src/server/redis/main.c @@ -0,0 +1,203 @@ +#include "admin/process.h" +#include "setting.h" +#include "stats.h" + +#include "time/time.h" +#include "util/util.h" + +#include + +#include +#include +#include +#include +#include + +struct data_processor worker_processor = { + redis_process_read, + redis_process_write, + redis_process_error, +}; + +static void +show_usage(void) +{ + log_stdout( + "Usage:" CRLF + " pelikan_redis [option|config]" CRLF + ); + log_stdout( + "Description:" CRLF + " pelikan_redis is one of the unified cache backends. " CRLF + " It uses slab-based storage for various data types. " CRLF + " It speaks the RESP protocol." CRLF + ); + log_stdout( + "Command-line options:" CRLF + " -h, --help show this message" CRLF + " -v, --version show version number" CRLF + " -c, --config list & describe all options in config" CRLF + " -s, --stats list & describe all metrics in stats" CRLF + ); + log_stdout( + "Example:" CRLF + " pelikan_redis redis.conf" CRLF CRLF + "Sample config files can be found under the config dir." CRLF + ); +} + +static void +teardown(void) +{ + core_worker_teardown(); + core_server_teardown(); + core_admin_teardown(); + admin_process_teardown(); + process_teardown(); + slab_teardown(); + compose_teardown(); + parse_teardown(); + response_teardown(); + request_teardown(); + procinfo_teardown(); + time_teardown(); + + timing_wheel_teardown(); + tcp_teardown(); + sockio_teardown(); + event_teardown(); + dbuf_teardown(); + buf_teardown(); + + debug_teardown(); + log_teardown(); +} + +static void +setup(void) +{ + char *fname = NULL; + uint64_t intvl; + + if (atexit(teardown) != 0) { + log_stderr("cannot register teardown procedure with atexit()"); + exit(EX_OSERR); /* only failure comes from NOMEM */ + } + + /* Setup logging first */ + log_setup(&stats.log); + if (debug_setup(&setting.debug) != CC_OK) { + log_stderr("debug log setup failed"); + exit(EX_CONFIG); + } + + /* setup top-level application options */ + if (option_bool(&setting.redis.daemonize)) { + daemonize(); + } + fname = option_str(&setting.redis.pid_filename); + if (fname != NULL) { + /* to get the correct pid, call create_pidfile after daemonize */ + create_pidfile(fname); + } + + /* setup library modules */ + buf_setup(&setting.buf, &stats.buf); + dbuf_setup(&setting.dbuf, &stats.dbuf); + event_setup(&stats.event); + sockio_setup(&setting.sockio, &stats.sockio); + tcp_setup(&setting.tcp, &stats.tcp); + timing_wheel_setup(&stats.timing_wheel); + + /* setup pelikan modules */ + time_setup(); + procinfo_setup(&stats.procinfo); + request_setup(&setting.request, &stats.request); + response_setup(&setting.response, &stats.response); + parse_setup(&stats.parse_req, NULL); + compose_setup(NULL, &stats.compose_rsp); + slab_setup(&setting.slab, &stats.slab); + process_setup(&setting.process, &stats.process); + admin_process_setup(); + core_admin_setup(&setting.admin); + core_server_setup(&setting.server, &stats.server); + core_worker_setup(&setting.worker, &stats.worker); + + /* adding recurring events to maintenance/admin thread */ + intvl = option_uint(&setting.redis.dlog_intvl); + if (core_admin_register(intvl, debug_log_flush, NULL) == NULL) { + log_stderr("Could not register timed event to flush debug log"); + goto error; + } + + return; + +error: + if (fname != NULL) { + remove_pidfile(fname); + } + + /* since we registered teardown with atexit, it'll be called upon exit */ + exit(EX_CONFIG); +} + +int +main(int argc, char **argv) +{ + rstatus_i status = CC_OK;; + FILE *fp = NULL; + + if (argc > 2) { + show_usage(); + exit(EX_USAGE); + } + + if (argc == 1) { + log_stderr("launching server with default values."); + } else { + /* argc == 2 */ + if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) { + show_usage(); + exit(EX_OK); + } + if (strcmp(argv[1], "-v") == 0 || strcmp(argv[1], "--version") == 0) { + show_version(); + exit(EX_OK); + } + if (strcmp(argv[1], "-c") == 0 || strcmp(argv[1], "--config") == 0) { + option_describe_all((struct option *)&setting, nopt); + exit(EX_OK); + } + if (strcmp(argv[1], "-s") == 0 || strcmp(argv[1], "--stats") == 0) { + metric_describe_all((struct metric *)&stats, nmetric); + exit(EX_OK); + } + fp = fopen(argv[1], "r"); + if (fp == NULL) { + log_stderr("cannot open config: incorrect path or doesn't exist"); + exit(EX_DATAERR); + } + } + + if (option_load_default((struct option *)&setting, nopt) != CC_OK) { + log_stderr("failed to load default option values"); + exit(EX_CONFIG); + } + + if (fp != NULL) { + log_stderr("load config from %s", argv[1]); + status = option_load_file(fp, (struct option *)&setting, nopt); + fclose(fp); + } + if (status != CC_OK) { + log_stderr("failed to load config"); + exit(EX_DATAERR); + } + + setup(); + option_print_all((struct option *)&setting, nopt); + + core_run(&worker_processor); + + exit(EX_OK); +} diff --git a/src/server/redis/setting.c b/src/server/redis/setting.c new file mode 100644 index 000000000..2e2b9499b --- /dev/null +++ b/src/server/redis/setting.c @@ -0,0 +1,20 @@ +#include "setting.h" + +struct setting setting = { + { REDIS_OPTION(OPTION_INIT) }, + { ADMIN_OPTION(OPTION_INIT) }, + { SERVER_OPTION(OPTION_INIT) }, + { WORKER_OPTION(OPTION_INIT) }, + { PROCESS_OPTION(OPTION_INIT) }, + { REQUEST_OPTION(OPTION_INIT) }, + { RESPONSE_OPTION(OPTION_INIT) }, + { SLAB_OPTION(OPTION_INIT) }, + { ARRAY_OPTION(OPTION_INIT) }, + { BUF_OPTION(OPTION_INIT) }, + { DBUF_OPTION(OPTION_INIT) }, + { DEBUG_OPTION(OPTION_INIT) }, + { SOCKIO_OPTION(OPTION_INIT) }, + { TCP_OPTION(OPTION_INIT) }, +}; + +unsigned int nopt = OPTION_CARDINALITY(struct setting); diff --git a/src/server/redis/setting.h b/src/server/redis/setting.h new file mode 100644 index 000000000..a09322e28 --- /dev/null +++ b/src/server/redis/setting.h @@ -0,0 +1,50 @@ +#pragma once + +#include "data/process.h" + +#include "core/core.h" +#include "storage/slab/item.h" +#include "storage/slab/slab.h" +#include "protocol/data/redis_include.h" + +#include +#include +#include +#include +#include +#include +#include + +/* option related */ +/* name type default description */ +#define REDIS_OPTION(ACTION) \ + ACTION( daemonize, OPTION_TYPE_BOOL, false, "daemonize the process" )\ + ACTION( pid_filename, OPTION_TYPE_STR, NULL, "file storing the pid" )\ + ACTION( dlog_intvl, OPTION_TYPE_UINT, 500, "debug log flush interval(ms)" ) + +typedef struct { + REDIS_OPTION(OPTION_DECLARE) +} redis_options_st; + +struct setting { + /* top-level */ + redis_options_st redis; + /* application modules */ + admin_options_st admin; + server_options_st server; + worker_options_st worker; + process_options_st process; + request_options_st request; + response_options_st response; + slab_options_st slab; + /* ccommon libraries */ + array_options_st array; + buf_options_st buf; + dbuf_options_st dbuf; + debug_options_st debug; + sockio_options_st sockio; + tcp_options_st tcp; +}; + +extern struct setting setting; +extern unsigned int nopt; diff --git a/src/server/redis/stats.c b/src/server/redis/stats.c new file mode 100644 index 000000000..5dbcfa83f --- /dev/null +++ b/src/server/redis/stats.c @@ -0,0 +1,22 @@ +#include "stats.h" + +struct stats stats = { + { PROCINFO_METRIC(METRIC_INIT) }, + { PROCESS_METRIC(METRIC_INIT) }, + { PARSE_REQ_METRIC(METRIC_INIT) }, + { COMPOSE_RSP_METRIC(METRIC_INIT) }, + { REQUEST_METRIC(METRIC_INIT) }, + { RESPONSE_METRIC(METRIC_INIT) }, + { SLAB_METRIC(METRIC_INIT) }, + { CORE_SERVER_METRIC(METRIC_INIT) }, + { CORE_WORKER_METRIC(METRIC_INIT) }, + { BUF_METRIC(METRIC_INIT) }, + { DBUF_METRIC(METRIC_INIT) }, + { EVENT_METRIC(METRIC_INIT) }, + { LOG_METRIC(METRIC_INIT) }, + { SOCKIO_METRIC(METRIC_INIT) }, + { TCP_METRIC(METRIC_INIT) }, + { TIMING_WHEEL_METRIC(METRIC_INIT) }, +}; + +unsigned int nmetric = METRIC_CARDINALITY(stats); diff --git a/src/server/redis/stats.h b/src/server/redis/stats.h new file mode 100644 index 000000000..1239f75cf --- /dev/null +++ b/src/server/redis/stats.h @@ -0,0 +1,41 @@ +#pragma once + +#include "data/process.h" + +#include "core/core.h" +#include "protocol/data/redis_include.h" +#include "storage/slab/item.h" +#include "storage/slab/slab.h" +#include "util/procinfo.h" + +#include +#include +#include +#include +#include +#include