Compare commits
38 Commits
7ef14d586f
...
d1f92d3cad
Author | SHA1 | Date |
---|---|---|
Vitaliy Filippov | d1f92d3cad | |
Vitaliy Filippov | 5217fb7915 | |
Vitaliy Filippov | 0de8a04d06 | |
Vitaliy Filippov | 95f95bac7b | |
Vitaliy Filippov | 6419477cfd | |
Vitaliy Filippov | e5c22f5f27 | |
Vitaliy Filippov | 4de61f092b | |
Vitaliy Filippov | a762831882 | |
Vitaliy Filippov | a8bf2e29fe | |
Vitaliy Filippov | 8b190fd41c | |
Vitaliy Filippov | d6c55948cc | |
Vitaliy Filippov | fc9df30a00 | |
Vitaliy Filippov | af96c0dd3f | |
Vitaliy Filippov | b6cd7d69fe | |
Vitaliy Filippov | 9d60523f28 | |
Vitaliy Filippov | 0378739449 | |
Vitaliy Filippov | 847ee33b03 | |
Vitaliy Filippov | 956da75eba | |
Vitaliy Filippov | 8d30ebbe6e | |
Vitaliy Filippov | 649459d4d0 | |
Vitaliy Filippov | f44c54b49c | |
Vitaliy Filippov | 91c835fb61 | |
Vitaliy Filippov | 540ea4b055 | |
Vitaliy Filippov | 2fcc60f79b | |
Vitaliy Filippov | edfaee8148 | |
Vitaliy Filippov | dd53a52f1b | |
Vitaliy Filippov | 5e6c7c5f69 | |
Vitaliy Filippov | fc402c6a11 | |
Vitaliy Filippov | 9f31cd32d0 | |
Vitaliy Filippov | 5c465332f0 | |
Vitaliy Filippov | 8e7fce7e28 | |
Vitaliy Filippov | 7ed4f0e9cf | |
Vitaliy Filippov | 381630e670 | |
Vitaliy Filippov | a772954154 | |
Vitaliy Filippov | 73a0eaf784 | |
Vitaliy Filippov | aa79d1db1c | |
Vitaliy Filippov | a1fecb7eff | |
Vitaliy Filippov | ff74b19423 |
|
@ -185,6 +185,25 @@ target_link_libraries(vitastor-nbd
|
||||||
vitastor_client
|
vitastor_client
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# vitastor-kv
|
||||||
|
add_executable(vitastor-kv
|
||||||
|
kv_cli.cpp
|
||||||
|
kv_db.cpp
|
||||||
|
kv_db.h
|
||||||
|
)
|
||||||
|
target_link_libraries(vitastor-kv
|
||||||
|
vitastor_client
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(vitastor-kv-stress
|
||||||
|
kv_stress.cpp
|
||||||
|
kv_db.cpp
|
||||||
|
kv_db.h
|
||||||
|
)
|
||||||
|
target_link_libraries(vitastor-kv-stress
|
||||||
|
vitastor_client
|
||||||
|
)
|
||||||
|
|
||||||
# vitastor-nfs
|
# vitastor-nfs
|
||||||
add_executable(vitastor-nfs
|
add_executable(vitastor-nfs
|
||||||
nfs_proxy.cpp
|
nfs_proxy.cpp
|
||||||
|
|
|
@ -32,7 +32,7 @@ void blockstore_init_meta::handle_event(ring_data_t *data, int buf_num)
|
||||||
if (data->res < 0)
|
if (data->res < 0)
|
||||||
{
|
{
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
std::string("read metadata failed at offset ") + std::to_string(bufs[buf_num].offset) +
|
std::string("read metadata failed at offset ") + std::to_string(buf_num >= 0 ? bufs[buf_num].offset : last_read_offset) +
|
||||||
std::string(": ") + strerror(-data->res)
|
std::string(": ") + strerror(-data->res)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,7 @@ int blockstore_init_meta::loop()
|
||||||
throw std::runtime_error("Failed to allocate metadata read buffer");
|
throw std::runtime_error("Failed to allocate metadata read buffer");
|
||||||
// Read superblock
|
// Read superblock
|
||||||
GET_SQE();
|
GET_SQE();
|
||||||
|
last_read_offset = 0;
|
||||||
data->iov = { metadata_buffer, (size_t)bs->dsk.meta_block_size };
|
data->iov = { metadata_buffer, (size_t)bs->dsk.meta_block_size };
|
||||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
||||||
my_uring_prep_readv(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset);
|
my_uring_prep_readv(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset);
|
||||||
|
@ -100,6 +101,7 @@ resume_1:
|
||||||
{
|
{
|
||||||
printf("Initializing metadata area\n");
|
printf("Initializing metadata area\n");
|
||||||
GET_SQE();
|
GET_SQE();
|
||||||
|
last_read_offset = 0;
|
||||||
data->iov = (struct iovec){ metadata_buffer, (size_t)bs->dsk.meta_block_size };
|
data->iov = (struct iovec){ metadata_buffer, (size_t)bs->dsk.meta_block_size };
|
||||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
||||||
my_uring_prep_writev(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset);
|
my_uring_prep_writev(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset);
|
||||||
|
@ -259,9 +261,11 @@ resume_2:
|
||||||
next_offset = entries_to_zero[i]/entries_per_block;
|
next_offset = entries_to_zero[i]/entries_per_block;
|
||||||
for (j = i; j < entries_to_zero.size() && entries_to_zero[j]/entries_per_block == next_offset; j++) {}
|
for (j = i; j < entries_to_zero.size() && entries_to_zero[j]/entries_per_block == next_offset; j++) {}
|
||||||
GET_SQE();
|
GET_SQE();
|
||||||
|
last_read_offset = (1+next_offset)*bs->dsk.meta_block_size;
|
||||||
data->iov = { metadata_buffer, (size_t)bs->dsk.meta_block_size };
|
data->iov = { metadata_buffer, (size_t)bs->dsk.meta_block_size };
|
||||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
||||||
my_uring_prep_readv(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset + (1+next_offset)*bs->dsk.meta_block_size);
|
my_uring_prep_readv(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset + (1+next_offset)*bs->dsk.meta_block_size);
|
||||||
|
bs->ringloop->submit();
|
||||||
submitted++;
|
submitted++;
|
||||||
resume_5:
|
resume_5:
|
||||||
if (submitted > 0)
|
if (submitted > 0)
|
||||||
|
@ -278,6 +282,7 @@ resume_5:
|
||||||
data->iov = { metadata_buffer, (size_t)bs->dsk.meta_block_size };
|
data->iov = { metadata_buffer, (size_t)bs->dsk.meta_block_size };
|
||||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
||||||
my_uring_prep_writev(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset + (1+next_offset)*bs->dsk.meta_block_size);
|
my_uring_prep_writev(sqe, bs->dsk.meta_fd, &data->iov, 1, bs->dsk.meta_offset + (1+next_offset)*bs->dsk.meta_block_size);
|
||||||
|
bs->ringloop->submit();
|
||||||
submitted++;
|
submitted++;
|
||||||
resume_6:
|
resume_6:
|
||||||
if (submitted > 0)
|
if (submitted > 0)
|
||||||
|
@ -299,6 +304,7 @@ resume_6:
|
||||||
{
|
{
|
||||||
GET_SQE();
|
GET_SQE();
|
||||||
my_uring_prep_fsync(sqe, bs->dsk.meta_fd, IORING_FSYNC_DATASYNC);
|
my_uring_prep_fsync(sqe, bs->dsk.meta_fd, IORING_FSYNC_DATASYNC);
|
||||||
|
last_read_offset = 0;
|
||||||
data->iov = { 0 };
|
data->iov = { 0 };
|
||||||
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
data->callback = [this](ring_data_t *data) { handle_event(data, -1); };
|
||||||
submitted++;
|
submitted++;
|
||||||
|
|
|
@ -23,6 +23,7 @@ class blockstore_init_meta
|
||||||
struct ring_data_t *data;
|
struct ring_data_t *data;
|
||||||
uint64_t md_offset = 0;
|
uint64_t md_offset = 0;
|
||||||
uint64_t next_offset = 0;
|
uint64_t next_offset = 0;
|
||||||
|
uint64_t last_read_offset = 0;
|
||||||
uint64_t entries_loaded = 0;
|
uint64_t entries_loaded = 0;
|
||||||
unsigned entries_per_block = 0;
|
unsigned entries_per_block = 0;
|
||||||
int i = 0, j = 0;
|
int i = 0, j = 0;
|
||||||
|
|
|
@ -64,10 +64,6 @@ std::string validate_pool_config(json11::Json::object & new_cfg, json11::Json ol
|
||||||
(new_cfg["parity_chunks"].uint64_value() > 1 ? 1 : 0);
|
(new_cfg["parity_chunks"].uint64_value() > 1 ? 1 : 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (new_cfg["scheme"] != "ec")
|
|
||||||
{
|
|
||||||
new_cfg.erase("parity_chunks");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check integer values and unknown keys
|
// Check integer values and unknown keys
|
||||||
for (auto kv_it = new_cfg.begin(); kv_it != new_cfg.end(); )
|
for (auto kv_it = new_cfg.begin(); kv_it != new_cfg.end(); )
|
||||||
|
@ -118,6 +114,12 @@ std::string validate_pool_config(json11::Json::object & new_cfg, json11::Json ol
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check after merging
|
||||||
|
if (new_cfg["scheme"] != "ec")
|
||||||
|
{
|
||||||
|
new_cfg.erase("parity_chunks");
|
||||||
|
}
|
||||||
|
|
||||||
// Prevent autovivification of object keys. Now we don't modify the config, we just check it
|
// Prevent autovivification of object keys. Now we don't modify the config, we just check it
|
||||||
json11::Json cfg = new_cfg;
|
json11::Json cfg = new_cfg;
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
#include "cluster_client_impl.h"
|
#include "cluster_client_impl.h"
|
||||||
#include "http_client.h" // json_is_true
|
#include "http_client.h" // json_is_true
|
||||||
|
|
||||||
cluster_client_t::cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd, json11::Json & config)
|
cluster_client_t::cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd, json11::Json config)
|
||||||
{
|
{
|
||||||
wb = new writeback_cache_t();
|
wb = new writeback_cache_t();
|
||||||
|
|
||||||
|
@ -238,7 +238,8 @@ void cluster_client_t::erase_op(cluster_op_t *op)
|
||||||
// which may continue following SYNCs, but these SYNCs
|
// which may continue following SYNCs, but these SYNCs
|
||||||
// should know about the changed buffer state
|
// should know about the changed buffer state
|
||||||
// This is ugly but this is the way we do it
|
// This is ugly but this is the way we do it
|
||||||
std::function<void(cluster_op_t*)>(op->callback)(op);
|
auto cb = std::move(op->callback);
|
||||||
|
cb(op);
|
||||||
}
|
}
|
||||||
if (!(flags & OP_IMMEDIATE_COMMIT) || enable_writeback)
|
if (!(flags & OP_IMMEDIATE_COMMIT) || enable_writeback)
|
||||||
{
|
{
|
||||||
|
@ -248,7 +249,8 @@ void cluster_client_t::erase_op(cluster_op_t *op)
|
||||||
{
|
{
|
||||||
// Call callback at the end to avoid inconsistencies in prev_wait
|
// Call callback at the end to avoid inconsistencies in prev_wait
|
||||||
// if the callback adds more operations itself
|
// if the callback adds more operations itself
|
||||||
std::function<void(cluster_op_t*)>(op->callback)(op);
|
auto cb = std::move(op->callback);
|
||||||
|
cb(op);
|
||||||
}
|
}
|
||||||
if (flags & OP_FLUSH_BUFFER)
|
if (flags & OP_FLUSH_BUFFER)
|
||||||
{
|
{
|
||||||
|
@ -548,7 +550,8 @@ void cluster_client_t::execute(cluster_op_t *op)
|
||||||
op->opcode != OSD_OP_READ_BITMAP && op->opcode != OSD_OP_READ_CHAIN_BITMAP && op->opcode != OSD_OP_WRITE)
|
op->opcode != OSD_OP_READ_BITMAP && op->opcode != OSD_OP_READ_CHAIN_BITMAP && op->opcode != OSD_OP_WRITE)
|
||||||
{
|
{
|
||||||
op->retval = -EINVAL;
|
op->retval = -EINVAL;
|
||||||
std::function<void(cluster_op_t*)>(op->callback)(op);
|
auto cb = std::move(op->callback);
|
||||||
|
cb(op);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!pgs_loaded)
|
if (!pgs_loaded)
|
||||||
|
@ -570,7 +573,7 @@ void cluster_client_t::execute_internal(cluster_op_t *op)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (op->opcode == OSD_OP_WRITE && enable_writeback && !(op->flags & OP_FLUSH_BUFFER) &&
|
if (op->opcode == OSD_OP_WRITE && enable_writeback && !(op->flags & OP_FLUSH_BUFFER) &&
|
||||||
!op->version /* FIXME no CAS writeback */)
|
!op->version /* no CAS writeback */)
|
||||||
{
|
{
|
||||||
if (wb->writebacks_active >= client_max_writeback_iodepth)
|
if (wb->writebacks_active >= client_max_writeback_iodepth)
|
||||||
{
|
{
|
||||||
|
@ -586,12 +589,13 @@ void cluster_client_t::execute_internal(cluster_op_t *op)
|
||||||
wb->start_writebacks(this, 1);
|
wb->start_writebacks(this, 1);
|
||||||
}
|
}
|
||||||
op->retval = op->len;
|
op->retval = op->len;
|
||||||
std::function<void(cluster_op_t*)>(op->callback)(op);
|
auto cb = std::move(op->callback);
|
||||||
|
cb(op);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (op->opcode == OSD_OP_WRITE && !(op->flags & OP_IMMEDIATE_COMMIT))
|
if (op->opcode == OSD_OP_WRITE && !(op->flags & OP_IMMEDIATE_COMMIT))
|
||||||
{
|
{
|
||||||
if (!(op->flags & OP_FLUSH_BUFFER))
|
if (!(op->flags & OP_FLUSH_BUFFER) && !op->version /* no CAS write-repeat */)
|
||||||
{
|
{
|
||||||
wb->copy_write(op, CACHE_WRITTEN);
|
wb->copy_write(op, CACHE_WRITTEN);
|
||||||
}
|
}
|
||||||
|
@ -655,7 +659,8 @@ bool cluster_client_t::check_rw(cluster_op_t *op)
|
||||||
if (!pool_id)
|
if (!pool_id)
|
||||||
{
|
{
|
||||||
op->retval = -EINVAL;
|
op->retval = -EINVAL;
|
||||||
std::function<void(cluster_op_t*)>(op->callback)(op);
|
auto cb = std::move(op->callback);
|
||||||
|
cb(op);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
auto pool_it = st_cli.pool_config.find(pool_id);
|
auto pool_it = st_cli.pool_config.find(pool_id);
|
||||||
|
@ -663,7 +668,8 @@ bool cluster_client_t::check_rw(cluster_op_t *op)
|
||||||
{
|
{
|
||||||
// Pools are loaded, but this one is unknown
|
// Pools are loaded, but this one is unknown
|
||||||
op->retval = -EINVAL;
|
op->retval = -EINVAL;
|
||||||
std::function<void(cluster_op_t*)>(op->callback)(op);
|
auto cb = std::move(op->callback);
|
||||||
|
cb(op);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Check alignment
|
// Check alignment
|
||||||
|
@ -671,7 +677,8 @@ bool cluster_client_t::check_rw(cluster_op_t *op)
|
||||||
op->offset % pool_it->second.bitmap_granularity || op->len % pool_it->second.bitmap_granularity)
|
op->offset % pool_it->second.bitmap_granularity || op->len % pool_it->second.bitmap_granularity)
|
||||||
{
|
{
|
||||||
op->retval = -EINVAL;
|
op->retval = -EINVAL;
|
||||||
std::function<void(cluster_op_t*)>(op->callback)(op);
|
auto cb = std::move(op->callback);
|
||||||
|
cb(op);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (pool_it->second.immediate_commit == IMMEDIATE_ALL)
|
if (pool_it->second.immediate_commit == IMMEDIATE_ALL)
|
||||||
|
@ -684,7 +691,8 @@ bool cluster_client_t::check_rw(cluster_op_t *op)
|
||||||
if (ino_it != st_cli.inode_config.end() && ino_it->second.readonly)
|
if (ino_it != st_cli.inode_config.end() && ino_it->second.readonly)
|
||||||
{
|
{
|
||||||
op->retval = -EROFS;
|
op->retval = -EROFS;
|
||||||
std::function<void(cluster_op_t*)>(op->callback)(op);
|
auto cb = std::move(op->callback);
|
||||||
|
cb(op);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1189,7 +1197,7 @@ void cluster_client_t::handle_op_part(cluster_op_part_t *part)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else if (log_level > 0)
|
||||||
{
|
{
|
||||||
fprintf(
|
fprintf(
|
||||||
stderr, "%s operation failed on OSD %ju: retval=%jd (expected %d)\n",
|
stderr, "%s operation failed on OSD %ju: retval=%jd (expected %d)\n",
|
||||||
|
|
|
@ -123,7 +123,7 @@ public:
|
||||||
json11::Json::object cli_config, file_config, etcd_global_config;
|
json11::Json::object cli_config, file_config, etcd_global_config;
|
||||||
json11::Json::object config;
|
json11::Json::object config;
|
||||||
|
|
||||||
cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd, json11::Json & config);
|
cluster_client_t(ring_loop_t *ringloop, timerfd_manager_t *tfd, json11::Json config);
|
||||||
~cluster_client_t();
|
~cluster_client_t();
|
||||||
void execute(cluster_op_t *op);
|
void execute(cluster_op_t *op);
|
||||||
void execute_raw(osd_num_t osd_num, osd_op_t *op);
|
void execute_raw(osd_num_t osd_num, osd_op_t *op);
|
||||||
|
|
|
@ -0,0 +1,405 @@
|
||||||
|
// Copyright (c) Vitaliy Filippov, 2019+
|
||||||
|
// License: VNPL-1.1 (see README.md for details)
|
||||||
|
//
|
||||||
|
// Vitastor shared key/value database test CLI
|
||||||
|
|
||||||
|
#define _XOPEN_SOURCE
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
#include <netinet/tcp.h>
|
||||||
|
#include <sys/epoll.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
//#include <signal.h>
|
||||||
|
|
||||||
|
#include "epoll_manager.h"
|
||||||
|
#include "str_util.h"
|
||||||
|
#include "kv_db.h"
|
||||||
|
|
||||||
|
const char *exe_name = NULL;
|
||||||
|
|
||||||
|
class kv_cli_t
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
kv_dbw_t *db = NULL;
|
||||||
|
ring_loop_t *ringloop = NULL;
|
||||||
|
epoll_manager_t *epmgr = NULL;
|
||||||
|
cluster_client_t *cli = NULL;
|
||||||
|
bool interactive = false;
|
||||||
|
int in_progress = 0;
|
||||||
|
char *cur_cmd = NULL;
|
||||||
|
int cur_cmd_size = 0, cur_cmd_alloc = 0;
|
||||||
|
bool finished = false, eof = false;
|
||||||
|
json11::Json::object cfg;
|
||||||
|
|
||||||
|
~kv_cli_t();
|
||||||
|
|
||||||
|
static json11::Json::object parse_args(int narg, const char *args[]);
|
||||||
|
void run(const json11::Json::object & cfg);
|
||||||
|
void read_cmd();
|
||||||
|
void next_cmd();
|
||||||
|
void handle_cmd(const std::string & cmd, std::function<void()> cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
kv_cli_t::~kv_cli_t()
|
||||||
|
{
|
||||||
|
if (cur_cmd)
|
||||||
|
{
|
||||||
|
free(cur_cmd);
|
||||||
|
cur_cmd = NULL;
|
||||||
|
}
|
||||||
|
cur_cmd_alloc = 0;
|
||||||
|
if (db)
|
||||||
|
delete db;
|
||||||
|
if (cli)
|
||||||
|
{
|
||||||
|
cli->flush();
|
||||||
|
delete cli;
|
||||||
|
}
|
||||||
|
if (epmgr)
|
||||||
|
delete epmgr;
|
||||||
|
if (ringloop)
|
||||||
|
delete ringloop;
|
||||||
|
}
|
||||||
|
|
||||||
|
json11::Json::object kv_cli_t::parse_args(int narg, const char *args[])
|
||||||
|
{
|
||||||
|
json11::Json::object cfg;
|
||||||
|
for (int i = 1; i < narg; i++)
|
||||||
|
{
|
||||||
|
if (!strcmp(args[i], "-h") || !strcmp(args[i], "--help"))
|
||||||
|
{
|
||||||
|
printf(
|
||||||
|
"Vitastor Key/Value CLI\n"
|
||||||
|
"(c) Vitaliy Filippov, 2023+ (VNPL-1.1)\n"
|
||||||
|
"\n"
|
||||||
|
"USAGE: %s [--etcd_address ADDR] [OTHER OPTIONS]\n",
|
||||||
|
exe_name
|
||||||
|
);
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
else if (args[i][0] == '-' && args[i][1] == '-')
|
||||||
|
{
|
||||||
|
const char *opt = args[i]+2;
|
||||||
|
cfg[opt] = !strcmp(opt, "json") || i == narg-1 ? "1" : args[++i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
void kv_cli_t::run(const json11::Json::object & cfg)
|
||||||
|
{
|
||||||
|
// Create client
|
||||||
|
ringloop = new ring_loop_t(512);
|
||||||
|
epmgr = new epoll_manager_t(ringloop);
|
||||||
|
cli = new cluster_client_t(ringloop, epmgr->tfd, cfg);
|
||||||
|
db = new kv_dbw_t(cli);
|
||||||
|
// Load image metadata
|
||||||
|
while (!cli->is_ready())
|
||||||
|
{
|
||||||
|
ringloop->loop();
|
||||||
|
if (cli->is_ready())
|
||||||
|
break;
|
||||||
|
ringloop->wait();
|
||||||
|
}
|
||||||
|
// Run
|
||||||
|
fcntl(0, F_SETFL, fcntl(0, F_GETFL, 0) | O_NONBLOCK);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
epmgr->tfd->set_fd_handler(0, false, [this](int fd, int events)
|
||||||
|
{
|
||||||
|
if (events & EPOLLIN)
|
||||||
|
{
|
||||||
|
read_cmd();
|
||||||
|
}
|
||||||
|
if (events & EPOLLRDHUP)
|
||||||
|
{
|
||||||
|
epmgr->tfd->set_fd_handler(0, false, NULL);
|
||||||
|
finished = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
interactive = isatty(0);
|
||||||
|
if (interactive)
|
||||||
|
printf("> ");
|
||||||
|
}
|
||||||
|
catch (std::exception & e)
|
||||||
|
{
|
||||||
|
// Can't add to epoll, STDIN is probably a file
|
||||||
|
read_cmd();
|
||||||
|
}
|
||||||
|
while (!finished)
|
||||||
|
{
|
||||||
|
ringloop->loop();
|
||||||
|
if (!finished)
|
||||||
|
ringloop->wait();
|
||||||
|
}
|
||||||
|
// Destroy the client
|
||||||
|
delete db;
|
||||||
|
db = NULL;
|
||||||
|
cli->flush();
|
||||||
|
delete cli;
|
||||||
|
delete epmgr;
|
||||||
|
delete ringloop;
|
||||||
|
cli = NULL;
|
||||||
|
epmgr = NULL;
|
||||||
|
ringloop = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void kv_cli_t::read_cmd()
|
||||||
|
{
|
||||||
|
if (!cur_cmd_alloc)
|
||||||
|
{
|
||||||
|
cur_cmd_alloc = 65536;
|
||||||
|
cur_cmd = (char*)malloc_or_die(cur_cmd_alloc);
|
||||||
|
}
|
||||||
|
while (cur_cmd_size < cur_cmd_alloc)
|
||||||
|
{
|
||||||
|
int r = read(0, cur_cmd+cur_cmd_size, cur_cmd_alloc-cur_cmd_size);
|
||||||
|
if (r < 0 && errno != EAGAIN)
|
||||||
|
fprintf(stderr, "Error reading from stdin: %s\n", strerror(errno));
|
||||||
|
if (r > 0)
|
||||||
|
cur_cmd_size += r;
|
||||||
|
if (r == 0)
|
||||||
|
eof = true;
|
||||||
|
if (r <= 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
next_cmd();
|
||||||
|
}
|
||||||
|
|
||||||
|
void kv_cli_t::next_cmd()
|
||||||
|
{
|
||||||
|
if (in_progress > 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int pos = 0;
|
||||||
|
for (; pos < cur_cmd_size; pos++)
|
||||||
|
{
|
||||||
|
if (cur_cmd[pos] == '\n' || cur_cmd[pos] == '\r')
|
||||||
|
{
|
||||||
|
auto cmd = trim(std::string(cur_cmd, pos));
|
||||||
|
pos++;
|
||||||
|
memmove(cur_cmd, cur_cmd+pos, cur_cmd_size-pos);
|
||||||
|
cur_cmd_size -= pos;
|
||||||
|
in_progress++;
|
||||||
|
handle_cmd(cmd, [this]()
|
||||||
|
{
|
||||||
|
in_progress--;
|
||||||
|
if (interactive)
|
||||||
|
printf("> ");
|
||||||
|
next_cmd();
|
||||||
|
if (!in_progress)
|
||||||
|
read_cmd();
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (eof && !in_progress)
|
||||||
|
{
|
||||||
|
finished = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void kv_cli_t::handle_cmd(const std::string & cmd, std::function<void()> cb)
|
||||||
|
{
|
||||||
|
if (cmd == "")
|
||||||
|
{
|
||||||
|
cb();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto pos = cmd.find_first_of(" \t");
|
||||||
|
if (pos != std::string::npos)
|
||||||
|
{
|
||||||
|
while (pos < cmd.size()-1 && (cmd[pos+1] == ' ' || cmd[pos+1] == '\t'))
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
auto opname = strtolower(pos == std::string::npos ? cmd : cmd.substr(0, pos));
|
||||||
|
if (opname == "open")
|
||||||
|
{
|
||||||
|
uint64_t pool_id = 0;
|
||||||
|
inode_t inode_id = 0;
|
||||||
|
uint32_t kv_block_size = 0;
|
||||||
|
int scanned = sscanf(cmd.c_str() + pos+1, "%lu %lu %u", &pool_id, &inode_id, &kv_block_size);
|
||||||
|
if (scanned == 2)
|
||||||
|
{
|
||||||
|
kv_block_size = 4096;
|
||||||
|
}
|
||||||
|
if (scanned < 2 || !pool_id || !inode_id || !kv_block_size || (kv_block_size & (kv_block_size-1)) != 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: open <pool_id> <inode_id> [block_size]. Block size must be a power of 2. Default is 4096.\n");
|
||||||
|
cb();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cfg["kv_block_size"] = (uint64_t)kv_block_size;
|
||||||
|
db->open(INODE_WITH_POOL(pool_id, inode_id), cfg, [=](int res)
|
||||||
|
{
|
||||||
|
if (res < 0)
|
||||||
|
fprintf(stderr, "Error opening index: %s (code %d)\n", strerror(-res), res);
|
||||||
|
else
|
||||||
|
fprintf(interactive ? stdout : stderr, "Index opened. Current size: %lu bytes\n", db->get_size());
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (opname == "config")
|
||||||
|
{
|
||||||
|
auto pos2 = cmd.find_first_of(" \t", pos+1);
|
||||||
|
if (pos2 == std::string::npos)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: config <property> <value>\n");
|
||||||
|
cb();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto key = trim(cmd.substr(pos+1, pos2-pos-1));
|
||||||
|
auto value = parse_size(trim(cmd.substr(pos2+1)));
|
||||||
|
if (key != "kv_memory_limit" &&
|
||||||
|
key != "kv_allocate_blocks" &&
|
||||||
|
key != "kv_evict_max_misses" &&
|
||||||
|
key != "kv_evict_attempts_per_level" &&
|
||||||
|
key != "kv_evict_unused_age" &&
|
||||||
|
key != "kv_log_level")
|
||||||
|
{
|
||||||
|
fprintf(
|
||||||
|
stderr, "Allowed properties: kv_memory_limit, kv_allocate_blocks,"
|
||||||
|
" kv_evict_max_misses, kv_evict_attempts_per_level, kv_evict_unused_age, kv_log_level\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cfg[key] = value;
|
||||||
|
db->set_config(cfg);
|
||||||
|
}
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
else if (opname == "get" || opname == "set" || opname == "del")
|
||||||
|
{
|
||||||
|
std::string key = scan_escaped(cmd, pos);
|
||||||
|
if (opname == "get" || opname == "del")
|
||||||
|
{
|
||||||
|
if (key == "")
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s <key>\n", opname.c_str());
|
||||||
|
cb();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (opname == "get")
|
||||||
|
{
|
||||||
|
db->get(key, [this, cb](int res, const std::string & value)
|
||||||
|
{
|
||||||
|
if (res < 0)
|
||||||
|
fprintf(stderr, "Error: %s (code %d)\n", strerror(-res), res);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
write(1, value.c_str(), value.size());
|
||||||
|
write(1, "\n", 1);
|
||||||
|
}
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
db->del(key, [this, cb](int res)
|
||||||
|
{
|
||||||
|
if (res < 0)
|
||||||
|
fprintf(stderr, "Error: %s (code %d)\n", strerror(-res), res);
|
||||||
|
else
|
||||||
|
fprintf(interactive ? stdout : stderr, "OK\n");
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (key == "" || pos >= cmd.size())
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: set <key> <value>\n");
|
||||||
|
cb();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto value = trim(cmd.substr(pos));
|
||||||
|
db->set(key, value, [this, cb](int res)
|
||||||
|
{
|
||||||
|
if (res < 0)
|
||||||
|
fprintf(stderr, "Error: %s (code %d)\n", strerror(-res), res);
|
||||||
|
else
|
||||||
|
fprintf(interactive ? stdout : stderr, "OK\n");
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (opname == "list" || opname == "dump")
|
||||||
|
{
|
||||||
|
bool dump = opname == "dump";
|
||||||
|
std::string start, end;
|
||||||
|
if (pos != std::string::npos)
|
||||||
|
{
|
||||||
|
auto pos2 = cmd.find_first_of(" \t", pos+1);
|
||||||
|
if (pos2 != std::string::npos)
|
||||||
|
{
|
||||||
|
start = trim(cmd.substr(pos+1, pos2-pos-1));
|
||||||
|
end = trim(cmd.substr(pos2+1));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
start = trim(cmd.substr(pos+1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void *handle = db->list_start(start);
|
||||||
|
db->list_next(handle, [=](int res, const std::string & key, const std::string & value)
|
||||||
|
{
|
||||||
|
if (res < 0)
|
||||||
|
{
|
||||||
|
if (res != -ENOENT)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Error: %s (code %d)\n", strerror(-res), res);
|
||||||
|
}
|
||||||
|
db->list_close(handle);
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (dump)
|
||||||
|
printf("set %s %s\n", auto_addslashes(key).c_str(), value.c_str());
|
||||||
|
else
|
||||||
|
printf("%s = %s\n", key.c_str(), value.c_str());
|
||||||
|
db->list_next(handle, NULL);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (opname == "close")
|
||||||
|
{
|
||||||
|
db->close([=]()
|
||||||
|
{
|
||||||
|
fprintf(interactive ? stdout : stderr, "Index closed\n");
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (opname == "quit" || opname == "q")
|
||||||
|
{
|
||||||
|
::close(0);
|
||||||
|
finished = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(
|
||||||
|
stderr, "Unknown operation: %s. Supported operations:\n"
|
||||||
|
"open <pool_id> <inode_id> [block_size]\n"
|
||||||
|
"config <property> <value>\n"
|
||||||
|
"get <key>\nset <key> <value>\ndel <key>\n"
|
||||||
|
"list [<start> [end]]\ndump [<start> [end]]\n"
|
||||||
|
"close\nquit\n", opname.c_str()
|
||||||
|
);
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int narg, const char *args[])
|
||||||
|
{
|
||||||
|
setvbuf(stdout, NULL, _IONBF, 0);
|
||||||
|
setvbuf(stderr, NULL, _IONBF, 0);
|
||||||
|
exe_name = args[0];
|
||||||
|
kv_cli_t *p = new kv_cli_t();
|
||||||
|
p->run(kv_cli_t::parse_args(narg, args));
|
||||||
|
delete p;
|
||||||
|
return 0;
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,36 @@
|
||||||
|
// Copyright (c) Vitaliy Filippov, 2019+
|
||||||
|
// License: VNPL-1.1 (see README.md for details)
|
||||||
|
//
|
||||||
|
// Vitastor shared key/value database
|
||||||
|
// Parallel optimistic B-Tree O:-)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "cluster_client.h"
|
||||||
|
|
||||||
|
struct kv_db_t;
|
||||||
|
|
||||||
|
struct kv_dbw_t
|
||||||
|
{
|
||||||
|
kv_dbw_t(cluster_client_t *cli);
|
||||||
|
~kv_dbw_t();
|
||||||
|
|
||||||
|
void open(inode_t inode_id, json11::Json cfg, std::function<void(int)> cb);
|
||||||
|
void set_config(json11::Json cfg);
|
||||||
|
void close(std::function<void()> cb);
|
||||||
|
|
||||||
|
uint64_t get_size();
|
||||||
|
|
||||||
|
void get(const std::string & key, std::function<void(int res, const std::string & value)> cb,
|
||||||
|
bool allow_old_cached = false);
|
||||||
|
void set(const std::string & key, const std::string & value, std::function<void(int res)> cb,
|
||||||
|
std::function<bool(int res, const std::string & value)> cas_compare = NULL);
|
||||||
|
void del(const std::string & key, std::function<void(int res)> cb,
|
||||||
|
std::function<bool(int res, const std::string & value)> cas_compare = NULL);
|
||||||
|
|
||||||
|
void* list_start(const std::string & start);
|
||||||
|
void list_next(void *handle, std::function<void(int res, const std::string & key, const std::string & value)> cb);
|
||||||
|
void list_close(void *handle);
|
||||||
|
|
||||||
|
kv_db_t *db;
|
||||||
|
};
|
|
@ -0,0 +1,697 @@
|
||||||
|
// Copyright (c) Vitaliy Filippov, 2019+
|
||||||
|
// License: VNPL-1.1 (see README.md for details)
|
||||||
|
//
|
||||||
|
// Vitastor shared key/value database stress tester / benchmark
|
||||||
|
|
||||||
|
#define _XOPEN_SOURCE
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
#include <netinet/tcp.h>
|
||||||
|
#include <sys/epoll.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
//#include <signal.h>
|
||||||
|
|
||||||
|
#include "epoll_manager.h"
|
||||||
|
#include "str_util.h"
|
||||||
|
#include "kv_db.h"
|
||||||
|
|
||||||
|
const char *exe_name = NULL;
|
||||||
|
|
||||||
|
struct kv_test_listing_t
|
||||||
|
{
|
||||||
|
uint64_t count = 0, done = 0;
|
||||||
|
void *handle = NULL;
|
||||||
|
std::string next_after;
|
||||||
|
std::set<std::string> inflights;
|
||||||
|
timespec tv_begin;
|
||||||
|
bool error = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct kv_test_lat_t
|
||||||
|
{
|
||||||
|
const char *name = NULL;
|
||||||
|
uint64_t usec = 0, count = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct kv_test_stat_t
|
||||||
|
{
|
||||||
|
kv_test_lat_t get, add, update, del, list;
|
||||||
|
uint64_t list_keys = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class kv_test_t
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// Config
|
||||||
|
json11::Json::object kv_cfg;
|
||||||
|
std::string key_prefix, key_suffix;
|
||||||
|
uint64_t inode_id = 0;
|
||||||
|
uint64_t op_count = 1000000;
|
||||||
|
uint64_t runtime_sec = 0;
|
||||||
|
uint64_t parallelism = 4;
|
||||||
|
uint64_t reopen_prob = 1;
|
||||||
|
uint64_t get_prob = 30000;
|
||||||
|
uint64_t add_prob = 20000;
|
||||||
|
uint64_t update_prob = 20000;
|
||||||
|
uint64_t del_prob = 5000;
|
||||||
|
uint64_t list_prob = 300;
|
||||||
|
uint64_t min_key_len = 10;
|
||||||
|
uint64_t max_key_len = 70;
|
||||||
|
uint64_t min_value_len = 50;
|
||||||
|
uint64_t max_value_len = 300;
|
||||||
|
uint64_t min_list_count = 10;
|
||||||
|
uint64_t max_list_count = 1000;
|
||||||
|
uint64_t print_stats_interval = 1;
|
||||||
|
bool json_output = false;
|
||||||
|
uint64_t log_level = 1;
|
||||||
|
bool trace = false;
|
||||||
|
bool stop_on_error = false;
|
||||||
|
// FIXME: Multiple clients
|
||||||
|
kv_test_stat_t stat, prev_stat;
|
||||||
|
timespec prev_stat_time, start_stat_time;
|
||||||
|
|
||||||
|
// State
|
||||||
|
kv_dbw_t *db = NULL;
|
||||||
|
ring_loop_t *ringloop = NULL;
|
||||||
|
epoll_manager_t *epmgr = NULL;
|
||||||
|
cluster_client_t *cli = NULL;
|
||||||
|
ring_consumer_t consumer;
|
||||||
|
bool finished = false;
|
||||||
|
uint64_t total_prob = 0;
|
||||||
|
uint64_t ops_sent = 0, ops_done = 0;
|
||||||
|
int stat_timer_id = -1;
|
||||||
|
int in_progress = 0;
|
||||||
|
bool reopening = false;
|
||||||
|
std::set<kv_test_listing_t*> listings;
|
||||||
|
std::set<std::string> changing_keys;
|
||||||
|
std::map<std::string, std::string> values;
|
||||||
|
|
||||||
|
~kv_test_t();
|
||||||
|
|
||||||
|
static json11::Json::object parse_args(int narg, const char *args[]);
|
||||||
|
void parse_config(json11::Json cfg);
|
||||||
|
void run(json11::Json cfg);
|
||||||
|
void loop();
|
||||||
|
void print_stats(kv_test_stat_t & prev_stat, timespec & prev_stat_time);
|
||||||
|
void print_total_stats();
|
||||||
|
void start_change(const std::string & key);
|
||||||
|
void stop_change(const std::string & key);
|
||||||
|
void add_stat(kv_test_lat_t & stat, timespec tv_begin);
|
||||||
|
};
|
||||||
|
|
||||||
|
kv_test_t::~kv_test_t()
|
||||||
|
{
|
||||||
|
if (db)
|
||||||
|
delete db;
|
||||||
|
if (cli)
|
||||||
|
{
|
||||||
|
cli->flush();
|
||||||
|
delete cli;
|
||||||
|
}
|
||||||
|
if (epmgr)
|
||||||
|
delete epmgr;
|
||||||
|
if (ringloop)
|
||||||
|
delete ringloop;
|
||||||
|
}
|
||||||
|
|
||||||
|
json11::Json::object kv_test_t::parse_args(int narg, const char *args[])
|
||||||
|
{
|
||||||
|
json11::Json::object cfg;
|
||||||
|
for (int i = 1; i < narg; i++)
|
||||||
|
{
|
||||||
|
if (!strcmp(args[i], "-h") || !strcmp(args[i], "--help"))
|
||||||
|
{
|
||||||
|
printf(
|
||||||
|
"Vitastor Key/Value DB stress tester / benchmark\n"
|
||||||
|
"(c) Vitaliy Filippov, 2023+ (VNPL-1.1)\n"
|
||||||
|
"\n"
|
||||||
|
"USAGE: %s --pool_id POOL_ID --inode_id INODE_ID [OPTIONS]\n"
|
||||||
|
" --op_count 1000000\n"
|
||||||
|
" Total operations to run during test. 0 means unlimited\n"
|
||||||
|
" --key_prefix \"\"\n"
|
||||||
|
" Prefix for all keys read or written (to avoid collisions)\n"
|
||||||
|
" --key_suffix \"\"\n"
|
||||||
|
" Suffix for all keys read or written (to avoid collisions, but scan all DB)\n"
|
||||||
|
" --runtime 0\n"
|
||||||
|
" Run for this number of seconds. 0 means unlimited\n"
|
||||||
|
" --parallelism 4\n"
|
||||||
|
" Run this number of operations in parallel\n"
|
||||||
|
" --get_prob 30000\n"
|
||||||
|
" Fraction of key retrieve operations\n"
|
||||||
|
" --add_prob 20000\n"
|
||||||
|
" Fraction of key addition operations\n"
|
||||||
|
" --update_prob 20000\n"
|
||||||
|
" Fraction of key update operations\n"
|
||||||
|
" --del_prob 30000\n"
|
||||||
|
" Fraction of key delete operations\n"
|
||||||
|
" --list_prob 300\n"
|
||||||
|
" Fraction of listing operations\n"
|
||||||
|
" --min_key_len 10\n"
|
||||||
|
" Minimum key size in bytes\n"
|
||||||
|
" --max_key_len 70\n"
|
||||||
|
" Maximum key size in bytes\n"
|
||||||
|
" --min_value_len 50\n"
|
||||||
|
" Minimum value size in bytes\n"
|
||||||
|
" --max_value_len 300\n"
|
||||||
|
" Maximum value size in bytes\n"
|
||||||
|
" --min_list_count 10\n"
|
||||||
|
" Minimum number of keys read in listing (0 = all keys)\n"
|
||||||
|
" --max_list_count 1000\n"
|
||||||
|
" Maximum number of keys read in listing\n"
|
||||||
|
" --print_stats 1\n"
|
||||||
|
" Print operation statistics every this number of seconds\n"
|
||||||
|
" --json\n"
|
||||||
|
" JSON output\n"
|
||||||
|
" --stop_on_error 0\n"
|
||||||
|
" Stop on first execution error, mismatch, lost key or extra key during listing\n"
|
||||||
|
" --kv_memory_limit 128M\n"
|
||||||
|
" Maximum memory to use for vitastor-kv index cache\n"
|
||||||
|
" --kv_allocate_blocks 4\n"
|
||||||
|
" Number of PG blocks used for new tree block allocation in parallel\n"
|
||||||
|
" --kv_evict_max_misses 10\n"
|
||||||
|
" Eviction algorithm parameter: retry eviction from another random spot\n"
|
||||||
|
" if this number of keys is used currently or was used recently\n"
|
||||||
|
" --kv_evict_attempts_per_level 3\n"
|
||||||
|
" Retry eviction at most this number of times per tree level, starting\n"
|
||||||
|
" with bottom-most levels\n"
|
||||||
|
" --kv_evict_unused_age 1000\n"
|
||||||
|
" Evict only keys unused during this number of last operations\n"
|
||||||
|
" --kv_log_level 1\n"
|
||||||
|
" Log level. 0 = errors, 1 = warnings, 10 = trace operations\n",
|
||||||
|
exe_name
|
||||||
|
);
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
else if (args[i][0] == '-' && args[i][1] == '-')
|
||||||
|
{
|
||||||
|
const char *opt = args[i]+2;
|
||||||
|
cfg[opt] = !strcmp(opt, "json") || i == narg-1 ? "1" : args[++i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
void kv_test_t::parse_config(json11::Json cfg)
|
||||||
|
{
|
||||||
|
inode_id = INODE_WITH_POOL(cfg["pool_id"].uint64_value(), cfg["inode_id"].uint64_value());
|
||||||
|
if (cfg["op_count"].uint64_value() > 0)
|
||||||
|
op_count = cfg["op_count"].uint64_value();
|
||||||
|
key_prefix = cfg["key_prefix"].string_value();
|
||||||
|
key_suffix = cfg["key_suffix"].string_value();
|
||||||
|
if (cfg["runtime"].uint64_value() > 0)
|
||||||
|
runtime_sec = cfg["runtime"].uint64_value();
|
||||||
|
if (cfg["parallelism"].uint64_value() > 0)
|
||||||
|
parallelism = cfg["parallelism"].uint64_value();
|
||||||
|
if (!cfg["reopen_prob"].is_null())
|
||||||
|
reopen_prob = cfg["reopen_prob"].uint64_value();
|
||||||
|
if (!cfg["get_prob"].is_null())
|
||||||
|
get_prob = cfg["get_prob"].uint64_value();
|
||||||
|
if (!cfg["add_prob"].is_null())
|
||||||
|
add_prob = cfg["add_prob"].uint64_value();
|
||||||
|
if (!cfg["update_prob"].is_null())
|
||||||
|
update_prob = cfg["update_prob"].uint64_value();
|
||||||
|
if (!cfg["del_prob"].is_null())
|
||||||
|
del_prob = cfg["del_prob"].uint64_value();
|
||||||
|
if (!cfg["list_prob"].is_null())
|
||||||
|
list_prob = cfg["list_prob"].uint64_value();
|
||||||
|
if (!cfg["min_key_len"].is_null())
|
||||||
|
min_key_len = cfg["min_key_len"].uint64_value();
|
||||||
|
if (cfg["max_key_len"].uint64_value() > 0)
|
||||||
|
max_key_len = cfg["max_key_len"].uint64_value();
|
||||||
|
if (!cfg["min_value_len"].is_null())
|
||||||
|
min_value_len = cfg["min_value_len"].uint64_value();
|
||||||
|
if (cfg["max_value_len"].uint64_value() > 0)
|
||||||
|
max_value_len = cfg["max_value_len"].uint64_value();
|
||||||
|
if (!cfg["min_list_count"].is_null())
|
||||||
|
min_list_count = cfg["min_list_count"].uint64_value();
|
||||||
|
if (!cfg["max_list_count"].is_null())
|
||||||
|
max_list_count = cfg["max_list_count"].uint64_value();
|
||||||
|
if (!cfg["print_stats"].is_null())
|
||||||
|
print_stats_interval = cfg["print_stats"].uint64_value();
|
||||||
|
if (!cfg["json"].is_null())
|
||||||
|
json_output = true;
|
||||||
|
if (!cfg["stop_on_error"].is_null())
|
||||||
|
stop_on_error = cfg["stop_on_error"].bool_value();
|
||||||
|
if (!cfg["kv_memory_limit"].is_null())
|
||||||
|
kv_cfg["kv_memory_limit"] = cfg["kv_memory_limit"];
|
||||||
|
if (!cfg["kv_allocate_blocks"].is_null())
|
||||||
|
kv_cfg["kv_allocate_blocks"] = cfg["kv_allocate_blocks"];
|
||||||
|
if (!cfg["kv_evict_max_misses"].is_null())
|
||||||
|
kv_cfg["kv_evict_max_misses"] = cfg["kv_evict_max_misses"];
|
||||||
|
if (!cfg["kv_evict_attempts_per_level"].is_null())
|
||||||
|
kv_cfg["kv_evict_attempts_per_level"] = cfg["kv_evict_attempts_per_level"];
|
||||||
|
if (!cfg["kv_evict_unused_age"].is_null())
|
||||||
|
kv_cfg["kv_evict_unused_age"] = cfg["kv_evict_unused_age"];
|
||||||
|
if (!cfg["kv_log_level"].is_null())
|
||||||
|
{
|
||||||
|
log_level = cfg["kv_log_level"].uint64_value();
|
||||||
|
trace = log_level >= 10;
|
||||||
|
kv_cfg["kv_log_level"] = cfg["kv_log_level"];
|
||||||
|
}
|
||||||
|
total_prob = reopen_prob+get_prob+add_prob+update_prob+del_prob+list_prob;
|
||||||
|
stat.get.name = "get";
|
||||||
|
stat.add.name = "add";
|
||||||
|
stat.update.name = "update";
|
||||||
|
stat.del.name = "del";
|
||||||
|
stat.list.name = "list";
|
||||||
|
}
|
||||||
|
|
||||||
|
void kv_test_t::run(json11::Json cfg)
|
||||||
|
{
|
||||||
|
srand48(time(NULL));
|
||||||
|
parse_config(cfg);
|
||||||
|
// Create client
|
||||||
|
ringloop = new ring_loop_t(512);
|
||||||
|
epmgr = new epoll_manager_t(ringloop);
|
||||||
|
cli = new cluster_client_t(ringloop, epmgr->tfd, cfg);
|
||||||
|
db = new kv_dbw_t(cli);
|
||||||
|
// Load image metadata
|
||||||
|
while (!cli->is_ready())
|
||||||
|
{
|
||||||
|
ringloop->loop();
|
||||||
|
if (cli->is_ready())
|
||||||
|
break;
|
||||||
|
ringloop->wait();
|
||||||
|
}
|
||||||
|
// Run
|
||||||
|
reopening = true;
|
||||||
|
db->open(inode_id, kv_cfg, [this](int res)
|
||||||
|
{
|
||||||
|
reopening = false;
|
||||||
|
if (res < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Open index: %d (%s)\n", res, strerror(-res));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if (trace)
|
||||||
|
printf("Index opened\n");
|
||||||
|
ringloop->wakeup();
|
||||||
|
});
|
||||||
|
consumer.loop = [this]() { loop(); };
|
||||||
|
ringloop->register_consumer(&consumer);
|
||||||
|
if (print_stats_interval)
|
||||||
|
stat_timer_id = epmgr->tfd->set_timer(print_stats_interval*1000, true, [this](int) { print_stats(prev_stat, prev_stat_time); });
|
||||||
|
clock_gettime(CLOCK_REALTIME, &start_stat_time);
|
||||||
|
prev_stat_time = start_stat_time;
|
||||||
|
while (!finished)
|
||||||
|
{
|
||||||
|
ringloop->loop();
|
||||||
|
if (!finished)
|
||||||
|
ringloop->wait();
|
||||||
|
}
|
||||||
|
if (stat_timer_id >= 0)
|
||||||
|
epmgr->tfd->clear_timer(stat_timer_id);
|
||||||
|
ringloop->unregister_consumer(&consumer);
|
||||||
|
// Print total stats
|
||||||
|
print_total_stats();
|
||||||
|
// Destroy the client
|
||||||
|
delete db;
|
||||||
|
db = NULL;
|
||||||
|
cli->flush();
|
||||||
|
delete cli;
|
||||||
|
delete epmgr;
|
||||||
|
delete ringloop;
|
||||||
|
cli = NULL;
|
||||||
|
epmgr = NULL;
|
||||||
|
ringloop = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@+/";
|
||||||
|
|
||||||
|
std::string random_str(int len)
|
||||||
|
{
|
||||||
|
std::string str;
|
||||||
|
str.resize(len);
|
||||||
|
for (int i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
str[i] = base64_chars[lrand48() % 64];
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
void kv_test_t::loop()
|
||||||
|
{
|
||||||
|
if (reopening)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ops_done >= op_count)
|
||||||
|
{
|
||||||
|
finished = true;
|
||||||
|
}
|
||||||
|
while (!finished && ops_sent < op_count && in_progress < parallelism)
|
||||||
|
{
|
||||||
|
uint64_t dice = (lrand48() % total_prob);
|
||||||
|
if (dice < reopen_prob)
|
||||||
|
{
|
||||||
|
reopening = true;
|
||||||
|
db->close([this]()
|
||||||
|
{
|
||||||
|
if (trace)
|
||||||
|
printf("Index closed\n");
|
||||||
|
db->open(inode_id, kv_cfg, [this](int res)
|
||||||
|
{
|
||||||
|
reopening = false;
|
||||||
|
if (res < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: Reopen index: %d (%s)\n", res, strerror(-res));
|
||||||
|
finished = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (trace)
|
||||||
|
printf("Index reopened\n");
|
||||||
|
ringloop->wakeup();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (dice < reopen_prob+get_prob)
|
||||||
|
{
|
||||||
|
// get existing
|
||||||
|
auto key = random_str(max_key_len);
|
||||||
|
auto k_it = values.lower_bound(key);
|
||||||
|
if (k_it == values.end())
|
||||||
|
continue;
|
||||||
|
key = k_it->first;
|
||||||
|
if (changing_keys.find(key) != changing_keys.end())
|
||||||
|
continue;
|
||||||
|
in_progress++;
|
||||||
|
ops_sent++;
|
||||||
|
if (trace)
|
||||||
|
printf("get %s\n", key.c_str());
|
||||||
|
timespec tv_begin;
|
||||||
|
clock_gettime(CLOCK_REALTIME, &tv_begin);
|
||||||
|
db->get(key, [this, key, tv_begin](int res, const std::string & value)
|
||||||
|
{
|
||||||
|
add_stat(stat.get, tv_begin);
|
||||||
|
ops_done++;
|
||||||
|
in_progress--;
|
||||||
|
auto it = values.find(key);
|
||||||
|
if (res != (it == values.end() ? -ENOENT : 0))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: get %s: %d (%s)\n", key.c_str(), res, strerror(-res));
|
||||||
|
if (stop_on_error)
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
else if (it != values.end() && value != it->second)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: get %s: mismatch: %s vs %s\n", key.c_str(), value.c_str(), it->second.c_str());
|
||||||
|
if (stop_on_error)
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
ringloop->wakeup();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (dice < reopen_prob+get_prob+add_prob+update_prob)
|
||||||
|
{
|
||||||
|
bool is_add = false;
|
||||||
|
std::string key;
|
||||||
|
if (dice < reopen_prob+get_prob+add_prob)
|
||||||
|
{
|
||||||
|
// add
|
||||||
|
is_add = true;
|
||||||
|
uint64_t key_len = min_key_len + (max_key_len > min_key_len ? lrand48() % (max_key_len-min_key_len) : 0);
|
||||||
|
key = key_prefix + random_str(key_len) + key_suffix;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// update
|
||||||
|
key = random_str(max_key_len);
|
||||||
|
auto k_it = values.lower_bound(key);
|
||||||
|
if (k_it == values.end())
|
||||||
|
continue;
|
||||||
|
key = k_it->first;
|
||||||
|
}
|
||||||
|
if (changing_keys.find(key) != changing_keys.end())
|
||||||
|
continue;
|
||||||
|
uint64_t value_len = min_value_len + (max_value_len > min_value_len ? lrand48() % (max_value_len-min_value_len) : 0);
|
||||||
|
auto value = random_str(value_len);
|
||||||
|
start_change(key);
|
||||||
|
ops_sent++;
|
||||||
|
in_progress++;
|
||||||
|
if (trace)
|
||||||
|
printf("set %s = %s\n", key.c_str(), value.c_str());
|
||||||
|
timespec tv_begin;
|
||||||
|
clock_gettime(CLOCK_REALTIME, &tv_begin);
|
||||||
|
db->set(key, value, [this, key, value, tv_begin, is_add](int res)
|
||||||
|
{
|
||||||
|
add_stat(is_add ? stat.add : stat.update, tv_begin);
|
||||||
|
stop_change(key);
|
||||||
|
ops_done++;
|
||||||
|
in_progress--;
|
||||||
|
if (res != 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: set %s = %s: %d (%s)\n", key.c_str(), value.c_str(), res, strerror(-res));
|
||||||
|
if (stop_on_error)
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
values[key] = value;
|
||||||
|
}
|
||||||
|
ringloop->wakeup();
|
||||||
|
}, NULL);
|
||||||
|
}
|
||||||
|
else if (dice < reopen_prob+get_prob+add_prob+update_prob+del_prob)
|
||||||
|
{
|
||||||
|
// delete
|
||||||
|
auto key = random_str(max_key_len);
|
||||||
|
auto k_it = values.lower_bound(key);
|
||||||
|
if (k_it == values.end())
|
||||||
|
continue;
|
||||||
|
key = k_it->first;
|
||||||
|
if (changing_keys.find(key) != changing_keys.end())
|
||||||
|
continue;
|
||||||
|
start_change(key);
|
||||||
|
ops_sent++;
|
||||||
|
in_progress++;
|
||||||
|
if (trace)
|
||||||
|
printf("del %s\n", key.c_str());
|
||||||
|
timespec tv_begin;
|
||||||
|
clock_gettime(CLOCK_REALTIME, &tv_begin);
|
||||||
|
db->del(key, [this, key, tv_begin](int res)
|
||||||
|
{
|
||||||
|
add_stat(stat.del, tv_begin);
|
||||||
|
stop_change(key);
|
||||||
|
ops_done++;
|
||||||
|
in_progress--;
|
||||||
|
if (res != 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: del %s: %d (%s)\n", key.c_str(), res, strerror(-res));
|
||||||
|
if (stop_on_error)
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
values.erase(key);
|
||||||
|
}
|
||||||
|
ringloop->wakeup();
|
||||||
|
}, NULL);
|
||||||
|
}
|
||||||
|
else if (dice < reopen_prob+get_prob+add_prob+update_prob+del_prob+list_prob)
|
||||||
|
{
|
||||||
|
// list
|
||||||
|
ops_sent++;
|
||||||
|
in_progress++;
|
||||||
|
auto key = random_str(max_key_len);
|
||||||
|
auto lst = new kv_test_listing_t;
|
||||||
|
auto k_it = values.lower_bound(key);
|
||||||
|
lst->count = min_list_count + (max_list_count > min_list_count ? lrand48() % (max_list_count-min_list_count) : 0);
|
||||||
|
lst->handle = db->list_start(k_it == values.begin() ? key_prefix : key);
|
||||||
|
lst->next_after = k_it == values.begin() ? key_prefix : key;
|
||||||
|
lst->inflights = changing_keys;
|
||||||
|
listings.insert(lst);
|
||||||
|
if (trace)
|
||||||
|
printf("list from %s\n", key.c_str());
|
||||||
|
clock_gettime(CLOCK_REALTIME, &lst->tv_begin);
|
||||||
|
db->list_next(lst->handle, [this, lst](int res, const std::string & key, const std::string & value)
|
||||||
|
{
|
||||||
|
if (log_level >= 11)
|
||||||
|
printf("list: %s = %s\n", key.c_str(), value.c_str());
|
||||||
|
if (res >= 0 && key_prefix.size() && (key.size() < key_prefix.size() ||
|
||||||
|
key.substr(0, key_prefix.size()) != key_prefix))
|
||||||
|
{
|
||||||
|
// stop at this key
|
||||||
|
res = -ENOENT;
|
||||||
|
}
|
||||||
|
if (res < 0 || (lst->count > 0 && lst->done >= lst->count))
|
||||||
|
{
|
||||||
|
add_stat(stat.list, lst->tv_begin);
|
||||||
|
if (res == 0)
|
||||||
|
{
|
||||||
|
// ok (done >= count)
|
||||||
|
}
|
||||||
|
else if (res != -ENOENT)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: list: %d (%s)\n", res, strerror(-res));
|
||||||
|
lst->error = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto k_it = lst->next_after == "" ? values.begin() : values.upper_bound(lst->next_after);
|
||||||
|
while (k_it != values.end())
|
||||||
|
{
|
||||||
|
while (k_it != values.end() && lst->inflights.find(k_it->first) != lst->inflights.end())
|
||||||
|
k_it++;
|
||||||
|
if (k_it != values.end())
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: list: missing key %s\n", (k_it++)->first.c_str());
|
||||||
|
lst->error = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lst->error && stop_on_error)
|
||||||
|
exit(1);
|
||||||
|
ops_done++;
|
||||||
|
in_progress--;
|
||||||
|
db->list_close(lst->handle);
|
||||||
|
delete lst;
|
||||||
|
listings.erase(lst);
|
||||||
|
ringloop->wakeup();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stat.list_keys++;
|
||||||
|
// Do not check modified keys in listing
|
||||||
|
// Listing may return their old or new state
|
||||||
|
if ((!key_suffix.size() || key.size() >= key_suffix.size() &&
|
||||||
|
key.substr(key.size()-key_suffix.size()) == key_suffix) &&
|
||||||
|
lst->inflights.find(key) == lst->inflights.end())
|
||||||
|
{
|
||||||
|
lst->done++;
|
||||||
|
auto k_it = lst->next_after == "" ? values.begin() : values.upper_bound(lst->next_after);
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
while (k_it != values.end() && lst->inflights.find(k_it->first) != lst->inflights.end())
|
||||||
|
{
|
||||||
|
k_it++;
|
||||||
|
}
|
||||||
|
if (k_it == values.end() || k_it->first > key)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: list: extra key %s\n", key.c_str());
|
||||||
|
lst->error = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (k_it->first < key)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: list: missing key %s\n", k_it->first.c_str());
|
||||||
|
lst->error = true;
|
||||||
|
lst->next_after = k_it->first;
|
||||||
|
k_it++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (k_it->second != value)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ERROR: list: mismatch: %s = %s but should be %s\n",
|
||||||
|
key.c_str(), value.c_str(), k_it->second.c_str());
|
||||||
|
lst->error = true;
|
||||||
|
}
|
||||||
|
lst->next_after = k_it->first;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db->list_next(lst->handle, NULL);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void kv_test_t::add_stat(kv_test_lat_t & stat, timespec tv_begin)
|
||||||
|
{
|
||||||
|
timespec tv_end;
|
||||||
|
clock_gettime(CLOCK_REALTIME, &tv_end);
|
||||||
|
int64_t usec = (tv_end.tv_sec - tv_begin.tv_sec)*1000000 +
|
||||||
|
(tv_end.tv_nsec - tv_begin.tv_nsec)/1000;
|
||||||
|
if (usec > 0)
|
||||||
|
{
|
||||||
|
stat.usec += usec;
|
||||||
|
stat.count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void kv_test_t::print_stats(kv_test_stat_t & prev_stat, timespec & prev_stat_time)
|
||||||
|
{
|
||||||
|
timespec cur_stat_time;
|
||||||
|
clock_gettime(CLOCK_REALTIME, &cur_stat_time);
|
||||||
|
int64_t usec = (cur_stat_time.tv_sec - prev_stat_time.tv_sec)*1000000 +
|
||||||
|
(cur_stat_time.tv_nsec - prev_stat_time.tv_nsec)/1000;
|
||||||
|
if (usec > 0)
|
||||||
|
{
|
||||||
|
kv_test_lat_t *lats[] = { &stat.get, &stat.add, &stat.update, &stat.del, &stat.list };
|
||||||
|
kv_test_lat_t *prev[] = { &prev_stat.get, &prev_stat.add, &prev_stat.update, &prev_stat.del, &prev_stat.list };
|
||||||
|
if (!json_output)
|
||||||
|
{
|
||||||
|
char buf[128] = { 0 };
|
||||||
|
for (int i = 0; i < sizeof(lats)/sizeof(lats[0]); i++)
|
||||||
|
{
|
||||||
|
snprintf(buf, sizeof(buf)-1, "%.1f %s/s (%lu us)", (lats[i]->count-prev[i]->count)*1000000.0/usec,
|
||||||
|
lats[i]->name, (lats[i]->usec-prev[i]->usec)/(lats[i]->count-prev[i]->count > 0 ? lats[i]->count-prev[i]->count : 1));
|
||||||
|
int k;
|
||||||
|
for (k = strlen(buf); k < strlen(lats[i]->name)+21; k++)
|
||||||
|
buf[k] = ' ';
|
||||||
|
buf[k] = 0;
|
||||||
|
printf("%s", buf);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int64_t runtime = (cur_stat_time.tv_sec - start_stat_time.tv_sec)*1000000 +
|
||||||
|
(cur_stat_time.tv_nsec - start_stat_time.tv_nsec)/1000;
|
||||||
|
printf("{\"runtime\":%.1f", (double)runtime/1000000.0);
|
||||||
|
for (int i = 0; i < sizeof(lats)/sizeof(lats[0]); i++)
|
||||||
|
{
|
||||||
|
if (lats[i]->count > prev[i]->count)
|
||||||
|
{
|
||||||
|
printf(
|
||||||
|
",\"%s\":{\"avg\":{\"iops\":%.1f,\"usec\":%lu},\"total\":{\"count\":%lu,\"usec\":%lu}}",
|
||||||
|
lats[i]->name, (lats[i]->count-prev[i]->count)*1000000.0/usec,
|
||||||
|
(lats[i]->usec-prev[i]->usec)/(lats[i]->count-prev[i]->count),
|
||||||
|
lats[i]->count, lats[i]->usec
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printf("}\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prev_stat = stat;
|
||||||
|
prev_stat_time = cur_stat_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
void kv_test_t::print_total_stats()
|
||||||
|
{
|
||||||
|
if (!json_output)
|
||||||
|
printf("Total:\n");
|
||||||
|
kv_test_stat_t start_stats;
|
||||||
|
timespec start_stat_time = this->start_stat_time;
|
||||||
|
print_stats(start_stats, start_stat_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
void kv_test_t::start_change(const std::string & key)
|
||||||
|
{
|
||||||
|
changing_keys.insert(key);
|
||||||
|
for (auto lst: listings)
|
||||||
|
{
|
||||||
|
lst->inflights.insert(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void kv_test_t::stop_change(const std::string & key)
|
||||||
|
{
|
||||||
|
changing_keys.erase(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int narg, const char *args[])
|
||||||
|
{
|
||||||
|
setvbuf(stdout, NULL, _IONBF, 0);
|
||||||
|
setvbuf(stderr, NULL, _IONBF, 0);
|
||||||
|
exe_name = args[0];
|
||||||
|
kv_test_t *p = new kv_test_t();
|
||||||
|
p->run(kv_test_t::parse_args(narg, args));
|
||||||
|
delete p;
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -348,3 +348,65 @@ std::vector<std::string> explode(const std::string & sep, const std::string & va
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// extract possibly double-quoted part of string with escape characters
|
||||||
|
std::string scan_escaped(const std::string & cmd, size_t & pos)
|
||||||
|
{
|
||||||
|
std::string key;
|
||||||
|
auto pos2 = cmd.find_first_not_of(" \t\r\n", pos);
|
||||||
|
if (pos2 == std::string::npos)
|
||||||
|
{
|
||||||
|
pos = cmd.size();
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
pos = pos2;
|
||||||
|
if (cmd[pos] != '"')
|
||||||
|
{
|
||||||
|
pos2 = cmd.find_first_of(" \t\r\n", pos);
|
||||||
|
pos2 = pos2 == std::string::npos ? cmd.size() : pos2;
|
||||||
|
key = cmd.substr(pos, pos2-pos);
|
||||||
|
pos2 = cmd.find_first_not_of(" \t\r\n", pos2);
|
||||||
|
pos = pos2 == std::string::npos ? cmd.size() : pos2;
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
pos++;
|
||||||
|
while (pos < cmd.size())
|
||||||
|
{
|
||||||
|
auto pos2 = cmd.find_first_of("\\\"", pos);
|
||||||
|
pos2 = pos2 == std::string::npos ? cmd.size() : pos2;
|
||||||
|
if (pos2 > pos)
|
||||||
|
key += cmd.substr(pos, pos2-pos);
|
||||||
|
pos = pos2;
|
||||||
|
if (pos >= cmd.size())
|
||||||
|
break;
|
||||||
|
if (cmd[pos] == '"')
|
||||||
|
{
|
||||||
|
pos++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (cmd[pos] == '\\')
|
||||||
|
{
|
||||||
|
if (pos < cmd.size()-1)
|
||||||
|
key += cmd[++pos];
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string auto_addslashes(const std::string & str)
|
||||||
|
{
|
||||||
|
auto pos = str.find_first_of("\\\"");
|
||||||
|
if (pos == std::string::npos)
|
||||||
|
return str;
|
||||||
|
std::string res = "\""+str.substr(0, pos)+"\\"+str[pos];
|
||||||
|
while (pos < str.size()-1)
|
||||||
|
{
|
||||||
|
auto pos2 = str.find_first_of("\\\"", pos+1);
|
||||||
|
if (pos2 == std::string::npos)
|
||||||
|
return res + str.substr(pos+1) + "\"";
|
||||||
|
res += str.substr(pos, pos2-pos)+"\\"+str[pos2];
|
||||||
|
pos = pos2;
|
||||||
|
}
|
||||||
|
return res+"\"";
|
||||||
|
}
|
||||||
|
|
|
@ -22,3 +22,5 @@ std::string str_repeat(const std::string & str, int times);
|
||||||
size_t utf8_length(const std::string & s);
|
size_t utf8_length(const std::string & s);
|
||||||
size_t utf8_length(const char *s);
|
size_t utf8_length(const char *s);
|
||||||
std::vector<std::string> explode(const std::string & sep, const std::string & value, bool trim);
|
std::vector<std::string> explode(const std::string & sep, const std::string & value, bool trim);
|
||||||
|
std::string scan_escaped(const std::string & cmd, size_t & pos);
|
||||||
|
std::string auto_addslashes(const std::string & str);
|
||||||
|
|
Loading…
Reference in New Issue