test: add hill testcase.

This commit is contained in:
Leo Yuriev 2017-05-17 20:10:56 +03:00
parent 8828e90ff9
commit aa80ef7e71
11 changed files with 795 additions and 131 deletions

View File

@ -14,8 +14,8 @@
#include "test.h"
void configure_actor(unsigned &lastid, const actor_testcase testcase,
const char *id_cstr, const actor_params &params) {
void configure_actor(unsigned &last_space_id, const actor_testcase testcase,
const char *space_id_cstr, const actor_params &params) {
unsigned wait4id = 0;
if (params.waitfor_nops) {
@ -33,40 +33,64 @@ void configure_actor(unsigned &lastid, const actor_testcase testcase,
failure("No previous waitable actor for %u-ops\n", params.waitfor_nops);
}
unsigned id = 0;
if (!id_cstr || strcmp(id_cstr, "auto") == 0)
id = lastid + 1;
unsigned space_id = 0;
if (!space_id_cstr || strcmp(space_id_cstr, "auto") == 0)
space_id = last_space_id + 1;
else {
char *end = nullptr;
errno = 0;
id = strtoul(id_cstr, &end, 0);
space_id = strtoul(space_id_cstr, &end, 0);
if (errno)
failure_perror("Expects an integer value for actor-id\n", errno);
failure_perror("Expects an integer value for space-id\n", errno);
if (end && *end)
failure("The '%s' is unexpected for actor-id\n", end);
failure("The '%s' is unexpected for space-id\n", end);
}
if (id < 1 || id > ACTOR_ID_MAX)
failure("Invalid actor-id %u\n", id);
lastid = id;
if (space_id > ACTOR_ID_MAX)
failure("Invalid space-id %u\n", space_id);
last_space_id = space_id;
log_trace("configure_actor: %u for %s", id, testcase2str(testcase));
global::actors.emplace_back(actor_config(testcase, params, id, wait4id));
log_trace("configure_actor: space %u for %s", space_id,
testcase2str(testcase));
global::actors.emplace_back(
actor_config(testcase, params, space_id, wait4id));
global::databases.insert(params.pathname_db);
}
void testcase_setup(const char *casename, actor_params &params,
unsigned &lastid) {
unsigned &last_space_id) {
if (strcmp(casename, "basic") == 0) {
log_notice(">>> testcase_setup(%s)", casename);
configure_actor(lastid, ac_hill, nullptr, params);
configure_actor(lastid, ac_jitter, nullptr, params);
configure_actor(lastid, ac_jitter, nullptr, params);
configure_actor(lastid, ac_jitter, nullptr, params);
configure_actor(last_space_id, ac_jitter, nullptr, params);
configure_actor(last_space_id, ac_hill, nullptr, params);
configure_actor(last_space_id, ac_jitter, nullptr, params);
configure_actor(last_space_id, ac_hill, nullptr, params);
configure_actor(last_space_id, ac_jitter, nullptr, params);
configure_actor(last_space_id, ac_hill, nullptr, params);
log_notice("<<< testcase_setup(%s): done", casename);
} else {
failure("unknown testcase `%s`", casename);
}
}
void keycase_setup(const char *casename, actor_params &params) {
if (strcmp(casename, "random") == 0 || strcmp(casename, "prng") == 0) {
log_notice(">>> keycase_setup(%s)", casename);
params.keygen.keycase = kc_random;
// TODO
log_notice("<<< keycase_setup(%s): done", casename);
} else if (strcmp(casename, "dashes") == 0 ||
strcmp(casename, "aside") == 0) {
log_notice(">>> keycase_setup(%s)", casename);
params.keygen.keycase = kc_dashes;
// TODO
log_notice("<<< keycase_setup(%s): done", casename);
} else if (strcmp(casename, "custom") == 0) {
log_notice("=== keycase_setup(%s): skip", casename);
params.keygen.keycase = kc_custom;
} else {
failure("unknown keycase `%s`", casename);
}
}
/* TODO */

View File

@ -176,6 +176,16 @@ bool parse_option(int argc, char *const argv[], int &narg, const char *option,
return true;
}
bool parse_option(int argc, char *const argv[], int &narg, const char *option,
uint8_t &value, const uint8_t minval, const uint8_t maxval) {
uint64_t huge;
if (!parse_option(argc, argv, narg, option, huge, no_scale, minval, maxval))
return false;
value = (uint8_t)huge;
return true;
}
bool parse_option(int argc, char *const argv[], int &narg, const char *option,
bool &value) {
const char *value_cstr = NULL;
@ -268,6 +278,8 @@ void dump(const char *title) {
logging::local_suffix indent(title);
for (auto i = global::actors.begin(); i != global::actors.end(); ++i) {
const std::string tableid =
i->space_id ? "MAINDB" : ("SUB#" + std::to_string(i->space_id));
log_info("#%u, testcase %s, space_id/table %u\n", i->actor_id,
testcase2str(i->testcase), i->space_id);
indent.push();
@ -284,8 +296,6 @@ void dump(const char *title) {
dump_verbs("mode", i->params.mode_flags, mode_bits);
dump_verbs("table", i->params.table_flags, table_bits);
log_info("seed %u\n", i->params.seed);
if (i->params.test_nops)
log_info("iterations/records %u\n", i->params.test_nops);
else
@ -298,6 +308,8 @@ void dump(const char *title) {
log_info("threads %u\n", i->params.nthreads);
log_info("keygen.case: %s\n", keygencase2str(i->params.keygen.keycase));
log_info("keygen.seed: %u\n", i->params.keygen.seed);
log_info("key: minlen %u, maxlen %u\n", i->params.keylen_min,
i->params.keylen_max);
log_info("data: minlen %u, maxlen %u\n", i->params.datalen_min,

View File

@ -34,6 +34,15 @@ enum actor_status {
const char *testcase2str(const actor_testcase);
const char *status2str(actor_status status);
enum keygen_case {
kc_random, /* [ 6.. 2.. 7.. 4.. 0.. 1.. 5.. 3.. ] */
kc_dashes, /* [ 0123.. 4567.. ] */
kc_custom,
/* TODO: more cases */
};
const char *keygencase2str(const keygen_case);
//-----------------------------------------------------------------------------
namespace config {
@ -65,17 +74,129 @@ bool parse_option(int argc, char *const argv[], int &narg, const char *option,
unsigned &value, const scale_mode scale,
const unsigned minval = 0, const unsigned maxval = INT32_MAX);
bool parse_option(int argc, char *const argv[], int &narg, const char *option,
uint8_t &value, const uint8_t minval = 0,
const uint8_t maxval = 255);
//-----------------------------------------------------------------------------
#pragma pack(push, 1)
struct keygen_params_pod {
keygen_case keycase;
/* Параметры генератора пар key-value.
*
* Ключи и значения генерируются по задаваемым параметрам на основе "плоской"
* исходной координаты. При этом, в общем случае, в процессе тестов исходная
* координата последовательно итерируется в заданном диапазоне, а необходимые
* паттерны/последовательности/узоры получаются за счет преобразования
* исходной координаты, согласно описанным ниже параметрам.
*
* Стоит отметить, что порядок описания параметров для удобства совпадает с
* порядком их использования, т.е. с порядком соответствующих преобразований.
*
* Второе важное замечание касается ограничений одновременной координированной
* генерации паттеров как для ключей, так и для значений. Суть в том, что
* такая возможность не нужна по следующим причинам:
* - libmdbx поддерживает два существенно различающихся вида таблиц,
* "уникальные" (без дубликатов и без multi-value), и так называемые
* "с дубликатами" (c multi-value).
* - Для таблиц "без дубликатов" только размер связанных к ключами значений
* (данных) оказывает влияния на работу движка, непосредственно содержимое
* данных не анализируется движком и не оказывает влияния на его работу.
* - Для таблиц "с дубликатами", при наличии более одного значения для
* некоторого ключа, формируется дочернее btree-поддерево. Это дерево
* формируется в отдельном "кусте" страниц и обслуживается независимо
* от окружения родительского ключа.
* - Таким образом, паттерн генерации значений имеет смысл только для
* таблиц "с дубликатами" и только в контексте одного значения ключа.
* Иначе говоря, нет смысла в со-координации генерации паттернов для
* ключей и значений. Более того, генерацию значений всегда необходимо
* рассматривать в контексте связки с одним значением ключа.
*
* width:
* Большинство тестов предполагают создание или итерирование некоторого
* количества записей. При этом требуется итерирование или генерация
* значений и ключей из некоторого ограниченного пространства вариантов.
*
* Параметр width задает такую ширину пространства вариантов в битах.
* Таким образом мощность пространства вариантов (пока) всегда равна
* степени двойки. Это ограничение можно снять, но ценой увеличения
* вычислительной сложности, включая потерю простоты и прозрачности.
*
* С другой стороны, не-битовый width может быть полезен:
* - Позволит генерировать ключи/значения в точно задаваемом диапазоне.
* Например, перебрать в псевдо-случайном порядке 10001 значение.
* - Позволит поровну разделять заданное пространство (диапазон)
* ключей/значений между количеством потоков некратным степени двойки.
*
* mesh и seed:
* Позволяют получить псевдо-случайные последовательности ключей/значений.
* Параметр mesh задает сколько младших бит исходной плоской координаты
* будет "перемешано" (инъективно отображено), а параметр seed позволяет
* выбрать конкретный вариант "перемешивания".
*
* Перемешивание выполняется при ненулевом значении mesh. Перемешивание
* реализуется посредством применения двух инъективных функций для
* заданного количества бит:
* - применяется первая инъективная функция;
* - к результату добавляется salt полученный из seed;
* - применяется вторая инъективная функция;
*
* Следует отметить, что mesh умышленно позволяет перемешать только младшую
* часть, что при ненулевом значении split (см далее) не позволяет получать
* псевдо-случайные значений ключей без псевдо-случайности в значениях.
*
* Такое ограничение соответствуют внутренней алгоритмике libmdbx. Проще
* говоря мы можем проверить движок псевдо-случайной последовательностью
* ключей на таблицах без дубликатов (без multi-value), а затем проверить
* корректность работу псевдо-случайной последовательностью значений на
* таблицах с дубликатами (с multi-value), опционально добавляя
* псевдо-случайности к последовательности ключей. Однако, нет смысла
* генерировать псевдо-случайные ключи, одновременно с формированием
* какого-либо паттерна в значениях, так как содержимое в данных либо
* не будет иметь значения (для таблиц без дубликатов), либо будет
* обрабатываться в отдельных btree-поддеревьях.
*
* rotate и offset:
* Для проверки слияния и разделения страниц внутри движка требуются
* генерация ключей/значений в виде не-смежных последовательностей, как-бы
* в виде "пунктира", который постепенно заполняет весь заданных диапазон.
*
* Параметры позволяют генерировать такой "пунктир". Соответственно rotate
* задает циклический сдвиг вправо, а offset задает смещение, точнее говоря
* сложение по модулю внутри диапазона заданного посредством width.
*
* Например, при rotate равном 1 (циклический сдвиг вправо на 1 бит),
* четные и нечетные исходные значения сложатся в две линейные
* последовательности, которые постепенно закроют старшую и младшую
* половины диапазона.
*
* split:
* Для таблиц без дубликатов (без multi-value ключей) фактически требуется
* генерация только ключей, а данные могут быть постоянным. Но для таблиц с
* дубликатами (с multi-value ключами) также требуется генерация значений.
*
* Ненулевое значение параметра split фактически включает генерацию значений,
* при этом значение split определяет сколько бит исходного абстрактного
* номера будет отрезано для генерации значения.
*/
uint8_t width;
uint8_t mesh;
uint8_t rotate;
uint8_t split;
uint32_t seed;
uint64_t offset;
};
struct actor_params_pod {
unsigned loglevel;
size_t mode_flags;
size_t table_flags;
uint64_t size;
unsigned seed;
unsigned test_duration;
unsigned test_nops;
@ -91,10 +212,11 @@ struct actor_params_pod {
unsigned delaystart;
unsigned waitfor_nops;
bool drop_table;
unsigned max_readers;
unsigned max_tables;
keygen_params_pod keygen;
bool drop_table;
};
struct actor_config_pod {

View File

@ -27,7 +27,196 @@ bool testcase_hill::setup() {
bool testcase_hill::run() {
db_open();
/* TODO */
txn_begin(false);
MDB_dbi dbi = db_table_open(true);
txn_end(false);
/* LY: тест "холмиком":
* - сначала наполняем таблицу циклическими CRUD-манипуляциями,
* которые в каждом цикле делают несколько операций, включая удаление,
* но в результате добавляют записи.
* - затем очищаем таблицу также CRUD-манипуляциями, но уже с другой
* пропорцией удалений.
*
* При этом очень многое зависит от порядка перебора ключей:
* - (псевдо)случайное распределение требуется лишь для полноты картины,
* но в целом не покрывает важных кейсов.
* - кроме (псевдо)случайного перебора требуется последовательное
* итерирование ключей интервалами различной ширины, с тем чтобы
* проверить различные варианты как разделения, так и слияния страниц
* внутри движка.
* - при не-уникальных ключах (MDB_DUPSORT с подвариантами), для каждого
* повтора внутри движка формируется вложенное btree-дерево,
* соответственно требуется соблюдение аналогичных принципов
* итерирования для значений.
*/
/* TODO: работа в несколько потоков */
keyvalue_maker.setup(config.params, 0 /* thread_number */);
keygen::buffer a_key = keygen::alloc(config.params.keylen_max);
keygen::buffer a_data_0 = keygen::alloc(config.params.datalen_max);
keygen::buffer a_data_1 = keygen::alloc(config.params.datalen_max);
keygen::buffer b_key = keygen::alloc(config.params.keylen_max);
keygen::buffer b_data = keygen::alloc(config.params.datalen_max);
const unsigned insert_flags = (config.params.table_flags & MDB_DUPSORT)
? MDB_NODUPDATA
: MDB_NODUPDATA | MDB_NOOVERWRITE;
const unsigned update_flags = MDB_CURRENT | MDB_NODUPDATA | MDB_NOOVERWRITE;
uint64_t serial_count = 0;
unsigned txn_nops = 0;
if (!txn_guard)
txn_begin(false);
while (should_continue()) {
const keygen::serial_t a_serial = serial_count;
if (unlikely(!keyvalue_maker.increment(serial_count, 1)))
failure("uphill: unexpected key-space overflow");
const keygen::serial_t b_serial = serial_count;
assert(b_serial > a_serial);
// создаем первую запись из пары
const keygen::serial_t age_shift = UINT64_C(1) << (a_serial % 31);
log_trace("uphill: insert-a (age %" PRIu64 ") %" PRIu64, age_shift,
a_serial);
generate_pair(a_serial, a_key, a_data_1, age_shift);
int rc = mdbx_put(txn_guard.get(), dbi, &a_key->value, &a_data_1->value,
insert_flags);
if (unlikely(rc != MDB_SUCCESS))
failure_perror("mdbx_put(insert-a.1)", rc);
if (++txn_nops >= config.params.batch_write) {
txn_restart(false, false);
txn_nops = 0;
}
// создаем вторую запись из пары
log_trace("uphill: insert-b %" PRIu64, b_serial);
generate_pair(b_serial, b_key, b_data, 0);
rc = mdbx_put(txn_guard.get(), dbi, &b_key->value, &b_data->value,
insert_flags);
if (unlikely(rc != MDB_SUCCESS))
failure_perror("mdbx_put(insert-b)", rc);
if (++txn_nops >= config.params.batch_write) {
txn_restart(false, false);
txn_nops = 0;
}
// обновляем данные в первой записи
log_trace("uphill: update-a (age %" PRIu64 "->0) %" PRIu64, age_shift,
a_serial);
generate_pair(a_serial, a_key, a_data_0, 0);
rc = mdbx_replace(txn_guard.get(), dbi, &a_key->value, &a_data_0->value,
&a_data_1->value, update_flags);
if (unlikely(rc != MDB_SUCCESS))
failure_perror("mdbx_put(update-a: 1->0)", rc);
if (++txn_nops >= config.params.batch_write) {
txn_restart(false, false);
txn_nops = 0;
}
// удаляем вторую запись
log_trace("uphill: delete-b %" PRIu64, b_serial);
rc = mdbx_del(txn_guard.get(), dbi, &b_key->value, &b_data->value);
if (unlikely(rc != MDB_SUCCESS))
failure_perror("mdbx_del(b)", rc);
if (++txn_nops >= config.params.batch_write) {
txn_restart(false, false);
txn_nops = 0;
}
report(1);
if (!keyvalue_maker.increment(serial_count, 1)) {
// дошли до границы пространства ключей
serial_count = a_serial;
goto overflow;
}
}
while (serial_count > 0) {
if (unlikely(!keyvalue_maker.increment(serial_count, -2)))
failure("downhill: unexpected key-space underflow");
overflow:
const keygen::serial_t a_serial = serial_count;
const keygen::serial_t b_serial = a_serial + 1;
assert(b_serial > a_serial);
// обновляем первую запись из пары
const keygen::serial_t age_shift = UINT64_C(1) << (a_serial % 31);
log_trace("downhill: update-a (age 0->%" PRIu64 ") %" PRIu64, age_shift,
a_serial);
generate_pair(a_serial, a_key, a_data_0, 0);
generate_pair(a_serial, a_key, a_data_1, age_shift);
if (a_serial == 808)
log_trace("!!!");
int rc = mdbx_replace(txn_guard.get(), dbi, &a_key->value, &a_data_1->value,
&a_data_0->value, update_flags);
if (unlikely(rc != MDB_SUCCESS))
failure_perror("mdbx_put(update-a: 0->1)", rc);
if (++txn_nops >= config.params.batch_write) {
txn_restart(false, false);
txn_nops = 0;
}
// создаем вторую запись из пары
log_trace("downhill: insert-b %" PRIu64, b_serial);
generate_pair(b_serial, b_key, b_data, 0);
rc = mdbx_put(txn_guard.get(), dbi, &b_key->value, &b_data->value,
insert_flags);
if (unlikely(rc != MDB_SUCCESS))
failure_perror("mdbx_put(insert-b)", rc);
if (++txn_nops >= config.params.batch_write) {
txn_restart(false, false);
txn_nops = 0;
}
// удаляем первую запись
log_trace("downhill: delete-a (age %" PRIu64 ") %" PRIu64, age_shift,
a_serial);
rc = mdbx_del(txn_guard.get(), dbi, &a_key->value, &a_data_1->value);
if (unlikely(rc != MDB_SUCCESS))
failure_perror("mdbx_del(a)", rc);
if (++txn_nops >= config.params.batch_write) {
txn_restart(false, false);
txn_nops = 0;
}
// удаляем вторую запись
log_trace("downhill: delete-b %" PRIu64, b_serial);
rc = mdbx_del(txn_guard.get(), dbi, &b_key->value, &b_data->value);
if (unlikely(rc != MDB_SUCCESS))
failure_perror("mdbx_del(b)", rc);
if (++txn_nops >= config.params.batch_write) {
txn_restart(false, false);
txn_nops = 0;
}
report(1);
}
if (txn_guard)
txn_end(false);
if (dbi) {
if (config.params.drop_table && !mode_readonly()) {
txn_begin(false);
db_table_drop(dbi);
txn_end(false);
} else
db_table_close(dbi);
}
return true;
}

View File

@ -16,18 +16,179 @@
namespace keygen {
size_t ffs_fallback(serial_t serial) {
size_t bit = sizeof(serial_t) * 8 - 1;
auto mask = (serial_t)1u << bit;
do {
if (serial & mask)
return bit;
--bit;
} while (mask >>= 1);
return 0;
static inline __pure_function serial_t mask(unsigned bits) {
assert(bits > 0 && bits <= serial_maxwith);
return serial_allones >> (serial_maxwith - bits);
}
void __hot make(const serial_t serial, const params_t &params, result_t &out) {
/* LY: https://en.wikipedia.org/wiki/Injective_function */
serial_t injective(const serial_t serial,
const unsigned bits /* at least serial_minwith (8) */,
const serial_t salt) {
assert(bits > serial_minwith && bits <= serial_maxwith);
/* LY: All these "magic" prime numbers were found
* and verified with a bit of brute force. */
static const uint64_t m[64 - serial_minwith] = {
/* 8 - 24 */
113, 157, 397, 653, 1753, 5641, 9697, 23873, 25693, 80833, 105953, 316937,
309277, 834497, 1499933, 4373441, 10184137,
/* 25 - 64 */
10184137, 17279209, 33990377, 67295161, 284404553, 1075238767, 6346721573,
6924051577, 19204053433, 45840188887, 53625693977, 73447827913,
141638870249, 745683604649, 1283334050489, 1100828289853, 2201656586197,
5871903036137, 11238507001417, 45264020802263, 105008404482889,
81921776907059, 199987980256399, 307207457507641, 946769023178273,
2420886491930041, 3601632139991929, 11984491914483833, 21805846439714153,
23171543400565993, 53353226456762893, 155627817337932409,
227827205384840249, 816509268558278821, 576933057762605689,
2623957345935638441, 5048241705479929949, 4634245581946485653};
static const uint8_t s[64 - serial_minwith] = {
/* 8 - 24 */
2, 3, 4, 4, 2, 4, 3, 3, 7, 3, 3, 4, 8, 3, 10, 3, 11,
/* 25 - 64 */
11, 9, 9, 9, 11, 10, 5, 14, 11, 16, 14, 12, 13, 16, 19, 10, 10, 21, 7, 20,
10, 14, 22, 19, 3, 21, 18, 19, 26, 24, 2, 21, 25, 29, 24, 10, 11, 14};
serial_t result = serial * m[bits - 8];
if (salt) {
const unsigned left = bits / 2;
const unsigned right = bits - left;
result = (result << left) | ((result & mask(bits)) >> right);
result = (result ^ salt) * m[bits - 8];
}
result ^= result << s[bits - 8];
result &= mask(bits);
log_trace("keygen-injective: serial %" PRIu64 " into %" PRIu64, serial,
result);
return result;
}
void __hot maker::pair(serial_t serial, const buffer &key, buffer &value,
serial_t value_age) {
assert(mapping.width >= serial_minwith && mapping.width <= serial_maxwith);
assert(mapping.split <= mapping.width);
assert(mapping.mesh <= mapping.width);
assert(mapping.rotate <= mapping.width);
assert(mapping.offset <= mask(mapping.width));
assert(!(key_essentials.flags & (MDB_INTEGERDUP | MDB_REVERSEDUP)));
assert(!(value_essentials.flags & (MDB_INTEGERKEY | MDB_REVERSEKEY)));
log_trace("keygen-pair: serial %" PRIu64 ", data-age %" PRIu64, serial,
value_age);
if (mapping.mesh >= serial_minwith) {
serial =
(serial & ~mask(mapping.mesh)) | injective(serial, mapping.mesh, salt);
log_trace("keygen-pair: mesh %" PRIu64, serial);
}
if (mapping.rotate) {
const unsigned right = mapping.rotate;
const unsigned left = mapping.width - right;
serial = (serial << left) | ((serial & mask(mapping.width)) >> right);
log_trace("keygen-pair: rotate %" PRIu64 ", 0x%" PRIx64, serial, serial);
}
serial = (serial + mapping.offset) & mask(mapping.width);
log_trace("keygen-pair: offset %" PRIu64, serial);
serial += base;
serial_t key_serial = serial;
serial_t value_serial = value_age;
if (mapping.split) {
key_serial = serial >> mapping.split;
value_serial =
(serial & mask(mapping.split)) | (value_age << mapping.split);
}
log_trace("keygen-pair: key %" PRIu64 ", value %" PRIu64, key_serial,
value_serial);
mk(key_serial, key_essentials, *key);
mk(value_serial, value_essentials, *value);
if (log_enabled(logging::trace)) {
char dump_key[128], dump_value[128];
log_trace("keygen-pair: key %s, value %s",
mdbx_dkey(&key->value, dump_key, sizeof(dump_key)),
mdbx_dkey(&value->value, dump_value, sizeof(dump_value)));
}
}
void maker::setup(const config::actor_params_pod &actor,
unsigned thread_number) {
key_essentials.flags = actor.table_flags & (MDB_INTEGERKEY | MDB_REVERSEKEY);
key_essentials.minlen = actor.keylen_min;
key_essentials.maxlen = actor.keylen_max;
value_essentials.flags =
actor.table_flags & (MDB_INTEGERDUP | MDB_REVERSEDUP);
value_essentials.minlen = actor.datalen_min;
value_essentials.maxlen = actor.datalen_max;
assert(thread_number < 2);
(void)thread_number;
mapping = actor.keygen;
salt = actor.keygen.seed * UINT64_C(14653293970879851569);
// FIXME: TODO
base = 0;
}
bool maker::increment(serial_t &serial, int delta) {
if (serial > mask(mapping.width)) {
log_extra("keygen-increment: %" PRIu64 " > %" PRIu64 ", overflow", serial,
mask(mapping.width));
return false;
}
serial_t target = serial + (int64_t)delta;
if (target > mask(mapping.width)) {
log_extra("keygen-increment: %" PRIu64 "%-d => %" PRIu64 ", overflow",
serial, delta, target);
return false;
}
log_extra("keygen-increment: %" PRIu64 "%-d => %" PRIu64 ", continue", serial,
delta, target);
serial = target;
return true;
}
//-----------------------------------------------------------------------------
size_t length(serial_t serial) {
size_t n = 0;
if (serial > UINT32_MAX) {
n = 4;
serial >>= 32;
}
if (serial > UINT16_MAX) {
n += 2;
serial >>= 16;
}
if (serial > UINT8_MAX) {
n += 1;
serial >>= 8;
}
return (serial > 0) ? n + 1 : n;
}
buffer alloc(size_t limit) {
result *ptr = (result *)malloc(sizeof(result) + limit);
if (unlikely(ptr == nullptr))
failure_perror("malloc(keyvalue_buffer)", errno);
ptr->value.iov_base = ptr->bytes;
ptr->value.iov_len = 0;
ptr->limit = limit;
return buffer(ptr);
}
void __hot maker::mk(const serial_t serial, const essentials &params,
result &out) {
assert(out.limit >= params.maxlen);
assert(params.maxlen >= params.minlen);
assert(params.maxlen >= length(serial));

View File

@ -15,6 +15,7 @@
#pragma once
#include "base.h"
#include "config.h"
#include "log.h"
#include "utils.h"
@ -42,25 +43,41 @@ namespace keygen {
* - частотное распределение по алфавиту;
* - абсолютное значение ключей или разность между отдельными значениями;
*
* Соответственно, схема генерации следующая:
* - для ключей вводится плоская одномерная "координата" uint64_t;
* - все преобразования (назначение диапазонов, переупорядочивание,
* коррекция распределения) выполняются только над "координатой";
* Соответственно, в общих чертах, схема генерации следующая:
* - вводится плоская одномерная "координата" uint64_t;
* - генерация специфических паттернов (последовательностей)
* реализуется посредством соответствующих преобразований "координат", при
* этом все подобные преобразования выполняются только над "координатой";
* - итоговая "координата" преобразуется в 8-байтное суррогатное значение
* ключа, при этом опционально суррогат может усекаться до ненулевых байт;
* - для получения ключей длиной более 8 байт суррогат дополняется
* фиксированной последовательностью;
* ключа;
* - для получения ключей длиной МЕНЕЕ 8 байт суррогат может усекаться
* до ненулевых байт, в том числе до нулевой длины;
* - для получения ключей длиной БОЛЕЕ 8 байт суррогат дополняется
* нулями или псевдослучайной последовательностью;
*
* Механизм генерации паттернов:
* - реализованный механизм является компромиссом между скоростью/простотой
* и гибкостью, необходимой для получения последовательностей, которых
* будет достаточно для проверки сценариев разделения и слияния страниц
* с данными внутри mdbx;
* - псевдо-случайные паттерны реализуются посредством набора инъективных
* отображающих функций;
* - не-псевдо-случайные паттерны реализуются посредством параметризируемого
* трех-этапного преобразования:
* 1) смещение (сложение) по модулю;
* 2) циклический сдвиг;
* 3) добавление абсолютного смещения (базы);
*/
typedef uint64_t serial_t;
struct params_t {
uint8_t minlen;
uint8_t flags;
uint16_t maxlen;
enum {
serial_minwith = 8,
serial_maxwith = sizeof(serial_t) * 8,
serial_allones = ~(serial_t)0
};
struct result_t {
struct result {
MDB_val value;
size_t limit;
union {
@ -70,54 +87,39 @@ struct result_t {
};
};
void make(const serial_t serial, const params_t &params, result_t &out);
//-----------------------------------------------------------------------------
static __inline void make(const serial_t serial, const params_t &params,
result_t &out, size_t limit) {
out.limit = limit;
make(serial, params, out);
}
struct buffer_deleter : public std::unary_function<void, result *> {
void operator()(result *buffer) const { free(buffer); }
};
size_t ffs_fallback(serial_t serial);
typedef std::unique_ptr<result, buffer_deleter> buffer;
static __inline size_t ffs(serial_t serial) {
size_t rc;
#ifdef __GNUC__
if (sizeof(serial) <= sizeof(int))
rc = __builtin_ffs((int)serial);
else if (sizeof(serial) == sizeof(long))
rc = __builtin_ffsl((long)serial);
else if (sizeof(serial) == sizeof(long long))
rc = __builtin_ffsll((long long)serial);
else
return ffs_fallback(serial);
#elif defined(_MSC_VER)
unsigned long index;
if (sizeof(serial) <= sizeof(unsigned long))
rc = _BitScanReverse(&index, (unsigned long)serial) ? index : 0;
else if (sizeof(serial) <= sizeof(unsigned __int64)) {
#if defined(_M_ARM64) || defined(_M_X64)
rc = _BitScanReverse64(&index, (unsigned __int64)serial) ? index : 0;
#else
size_t base = 0;
unsigned long value = (unsigned long)serial;
if ((unsigned __int64)serial > ULONG_MAX) {
base = 32;
value = (unsigned long)(serial >> 32);
}
rc = (_BitScanReverse(&index, value) ? index : 0) + base;
#endif /* _M_ARM64 || _M_X64 */
} else
return ffs_fallback(serial);
#else
return ffs_fallback(serial);
#endif
assert(rc == ffs_fallback(serial));
return rc;
}
buffer alloc(size_t limit);
static __inline size_t length(const serial_t serial) {
return (ffs(serial) + 7) >> 3;
}
class maker {
config::keygen_params_pod mapping;
serial_t base;
serial_t salt;
struct essentials {
uint8_t minlen;
uint8_t flags;
uint16_t maxlen;
} key_essentials, value_essentials;
static void mk(const serial_t serial, const essentials &params, result &out);
public:
maker() { memset(this, 0, sizeof(*this)); }
void pair(serial_t serial, const buffer &key, buffer &value,
serial_t value_age);
void setup(const config::actor_params_pod &actor, unsigned thread_number);
bool increment(serial_t &serial, int delta);
};
size_t length(serial_t serial);
} /* namespace keygen */

View File

@ -41,7 +41,14 @@ void actor_params::set_defaults(void) {
MDB_NOMEMINIT | MDBX_COALESCE | MDBX_LIFORECLAIM;
table_flags = MDB_DUPSORT;
size = 1024 * 1024;
seed = 1;
keygen.seed = 1;
keygen.keycase = kc_random;
keygen.width = 32;
keygen.mesh = 32;
keygen.split = keygen.width / 2;
keygen.rotate = 0;
keygen.offset = 0;
test_duration = 0;
test_nops = 1000;
@ -129,13 +136,13 @@ int main(int argc, char *const argv[]) {
params.set_defaults();
global::config::dump_config = true;
logging::setup((logging::loglevel)params.loglevel, "main");
unsigned lastid = 0;
unsigned last_space_id = 0;
for (int narg = 1; narg < argc; ++narg) {
const char *value = nullptr;
if (config::parse_option(argc, argv, narg, "case", &value)) {
testcase_setup(value, params, lastid);
testcase_setup(value, params, last_space_id);
continue;
}
if (config::parse_option(argc, argv, narg, "pathname", params.pathname_db))
@ -149,9 +156,30 @@ int main(int argc, char *const argv[]) {
if (config::parse_option(argc, argv, narg, "size", params.size,
config::binary, 4096 * 4))
continue;
if (config::parse_option(argc, argv, narg, "seed", params.seed,
config::no_scale))
if (config::parse_option(argc, argv, narg, "keygen.width",
params.keygen.width, 1, 64))
continue;
if (config::parse_option(argc, argv, narg, "keygen.mesh",
params.keygen.mesh, 1, 64))
continue;
if (config::parse_option(argc, argv, narg, "keygen.seed",
params.keygen.seed, config::no_scale))
continue;
if (config::parse_option(argc, argv, narg, "keygen.split",
params.keygen.split, 1, 64))
continue;
if (config::parse_option(argc, argv, narg, "keygen.rotate",
params.keygen.rotate, 1, 64))
continue;
if (config::parse_option(argc, argv, narg, "keygen.offset",
params.keygen.offset, config::binary))
continue;
if (config::parse_option(argc, argv, narg, "keygen.case", &value)) {
keycase_setup(value, params);
continue;
}
if (config::parse_option(argc, argv, narg, "repeat", params.nrepeat,
config::no_scale))
continue;
@ -225,20 +253,20 @@ int main(int argc, char *const argv[]) {
params.test_duration = 0;
continue;
}
if (config::parse_option(argc, argv, narg, "hill", &value)) {
configure_actor(lastid, ac_hill, value, params);
if (config::parse_option(argc, argv, narg, "hill", &value, "auto")) {
configure_actor(last_space_id, ac_hill, value, params);
continue;
}
if (config::parse_option(argc, argv, narg, "jitter", nullptr)) {
configure_actor(lastid, ac_jitter, value, params);
configure_actor(last_space_id, ac_jitter, value, params);
continue;
}
if (config::parse_option(argc, argv, narg, "dead.reader", nullptr)) {
configure_actor(lastid, ac_deadread, value, params);
configure_actor(last_space_id, ac_deadread, value, params);
continue;
}
if (config::parse_option(argc, argv, narg, "dead.writer", nullptr)) {
configure_actor(lastid, ac_deadwrite, value, params);
configure_actor(last_space_id, ac_deadwrite, value, params);
continue;
}
if (config::parse_option(argc, argv, narg, "failfast",
@ -246,7 +274,7 @@ int main(int argc, char *const argv[]) {
continue;
if (*argv[narg] != '-')
testcase_setup(argv[narg], params, lastid);
testcase_setup(argv[narg], params, last_space_id);
else
failure("Unknown option '%s'\n", argv[narg]);
}

View File

@ -17,6 +17,7 @@
const char *testcase2str(const actor_testcase testcase) {
switch (testcase) {
default:
assert(false);
return "?!";
case ac_none:
return "none";
@ -49,6 +50,20 @@ const char *status2str(actor_status status) {
}
}
const char *keygencase2str(const keygen_case keycase) {
switch (keycase) {
default:
assert(false);
return "?!";
case kc_random:
return "random";
case kc_dashes:
return "dashes";
case kc_custom:
return "custom";
}
}
//-----------------------------------------------------------------------------
static void mdbx_debug_logger(int type, const char *function, int line,
@ -67,7 +82,9 @@ static void mdbx_debug_logger(int type, const char *function, int line,
level = logging::failure;
}
if (logging::output(level, "mdbx: %s: ", function))
if (logging::output(level, strncmp(function, "mdbx_", 5) == 0 ? "%s: "
: "mdbx: %s: ",
function))
logging::feed(msg, args);
if (type & MDBX_DBG_ASSERT)
abort();
@ -87,26 +104,26 @@ void testcase::db_prepare() {
MDB_env *env = nullptr;
rc = mdbx_env_create(&env);
if (rc != MDB_SUCCESS)
if (unlikely(rc != MDB_SUCCESS))
failure_perror("mdbx_env_create()", rc);
assert(env != nullptr);
db_guard.reset(env);
rc = mdbx_env_set_userctx(env, this);
if (rc != MDB_SUCCESS)
if (unlikely(rc != MDB_SUCCESS))
failure_perror("mdbx_env_set_userctx()", rc);
rc = mdbx_env_set_maxreaders(env, config.params.max_readers);
if (rc != MDB_SUCCESS)
if (unlikely(rc != MDB_SUCCESS))
failure_perror("mdbx_env_set_maxreaders()", rc);
rc = mdbx_env_set_maxdbs(env, config.params.max_tables);
if (rc != MDB_SUCCESS)
if (unlikely(rc != MDB_SUCCESS))
failure_perror("mdbx_env_set_maxdbs()", rc);
rc = mdbx_env_set_mapsize(env, (size_t)config.params.size);
if (rc != MDB_SUCCESS)
if (unlikely(rc != MDB_SUCCESS))
failure_perror("mdbx_env_set_mapsize()", rc);
log_trace("<< db_prepare");
@ -119,7 +136,7 @@ void testcase::db_open() {
db_prepare();
int rc = mdbx_env_open(db_guard.get(), config.params.pathname_db.c_str(),
(unsigned)config.params.mode_flags, 0640);
if (rc != MDB_SUCCESS)
if (unlikely(rc != MDB_SUCCESS))
failure_perror("mdbx_env_open()", rc);
log_trace("<< db_open");
@ -140,7 +157,7 @@ void testcase::txn_begin(bool readonly) {
MDB_txn *txn = nullptr;
int rc =
mdbx_txn_begin(db_guard.get(), nullptr, readonly ? MDB_RDONLY : 0, &txn);
if (rc != MDB_SUCCESS)
if (unlikely(rc != MDB_SUCCESS))
failure_perror("mdbx_txn_begin()", rc);
txn_guard.reset(txn);
@ -154,17 +171,23 @@ void testcase::txn_end(bool abort) {
MDB_txn *txn = txn_guard.release();
if (abort) {
int rc = mdbx_txn_abort(txn);
if (rc != MDB_SUCCESS)
if (unlikely(rc != MDB_SUCCESS))
failure_perror("mdbx_txn_abort()", rc);
} else {
int rc = mdbx_txn_commit(txn);
if (rc != MDB_SUCCESS)
if (unlikely(rc != MDB_SUCCESS))
failure_perror("mdbx_txn_commit()", rc);
}
log_trace("<< txn_end(%s)", abort ? "abort" : "commit");
}
void testcase::txn_restart(bool abort, bool readonly) {
if (txn_guard)
txn_end(abort);
txn_begin(readonly);
}
bool testcase::wait4start() {
if (config.wait4id) {
log_trace(">> wait4start(%u)", config.wait4id);
@ -257,7 +280,7 @@ void testcase::fetch_canary() {
log_trace(">> fetch_canary");
int rc = mdbx_canary_get(txn_guard.get(), &canary_now);
if (rc != MDB_SUCCESS)
if (unlikely(rc != MDB_SUCCESS))
failure_perror("mdbx_canary_get()", rc);
if (canary_now.v < last.canary.v)
@ -283,10 +306,57 @@ void testcase::update_canary(uint64_t increment) {
canary_now.y += increment;
int rc = mdbx_canary_put(txn_guard.get(), &canary_now);
if (rc != MDB_SUCCESS)
if (unlikely(rc != MDB_SUCCESS))
failure_perror("mdbx_canary_put()", rc);
log_trace(">> update_canary: sequence = %" PRIu64, canary_now.y);
log_trace("<< update_canary: sequence = %" PRIu64, canary_now.y);
}
MDB_dbi testcase::db_table_open(bool create) {
log_trace(">> testcase::db_table_create");
char tablename_buf[16];
const char *tablename = nullptr;
if (config.space_id) {
int rc = snprintf(tablename_buf, sizeof(tablename_buf), "TBL%04u",
config.space_id);
if (rc < 4 || rc >= (int)sizeof(tablename_buf) - 1)
failure("snprintf(tablename): %d", rc);
tablename = tablename_buf;
}
log_verbose("use %s table", tablename ? tablename : "MAINDB");
MDB_dbi handle = 0;
int rc = mdbx_dbi_open(txn_guard.get(), tablename,
(create ? MDB_CREATE : 0) | config.params.table_flags,
&handle);
if (unlikely(rc != MDB_SUCCESS))
failure_perror("mdbx_dbi_open()", rc);
log_trace("<< testcase::db_table_create, handle %u", handle);
return handle;
}
void testcase::db_table_drop(MDB_dbi handle) {
log_trace(">> testcase::db_table_drop, handle %u", handle);
if (config.params.drop_table) {
int rc = mdbx_drop(txn_guard.get(), handle, true);
if (unlikely(rc != MDB_SUCCESS))
failure_perror("mdbx_drop()", rc);
log_trace("<< testcase::db_table_drop");
} else {
log_trace("<< testcase::db_table_drop: not needed");
}
}
void testcase::db_table_close(MDB_dbi handle) {
log_trace(">> testcase::db_table_close, handle %u", handle);
assert(!txn_guard);
int rc = mdbx_dbi_close(db_guard.get(), handle);
if (unlikely(rc != MDB_SUCCESS))
failure_perror("mdbx_dbi_close()", rc);
log_trace("<< testcase::db_table_close");
}
//-----------------------------------------------------------------------------

View File

@ -25,9 +25,10 @@
bool test_execute(const actor_config &config);
std::string thunk_param(const actor_config &config);
void testcase_setup(const char *casename, actor_params &params,
unsigned &lastid);
void configure_actor(unsigned &lastid, const actor_testcase testcase,
const char *id_cstr, const actor_params &params);
unsigned &last_space_id);
void configure_actor(unsigned &last_space_id, const actor_testcase testcase,
const char *space_id_cstr, const actor_params &params);
void keycase_setup(const char *casename, actor_params &params);
namespace global {
@ -87,6 +88,9 @@ protected:
size_t nops_completed;
chrono::time start_timestamp;
keygen::buffer key;
keygen::buffer data;
keygen::maker keyvalue_maker;
struct {
mdbx_canary canary;
@ -97,14 +101,29 @@ protected:
void db_close();
void txn_begin(bool readonly);
void txn_end(bool abort);
void txn_restart(bool abort, bool readonly);
void fetch_canary();
void update_canary(uint64_t increment);
MDB_dbi db_table_open(bool create);
void db_table_drop(MDB_dbi handle);
void db_table_close(MDB_dbi handle);
bool wait4start();
void report(size_t nops_done);
void signal();
bool should_continue() const;
void generate_pair(const keygen::serial_t serial, keygen::buffer &key,
keygen::buffer &value, keygen::serial_t data_age = 0) {
keyvalue_maker.pair(serial, key, value, data_age);
}
void generate_pair(const keygen::serial_t serial,
keygen::serial_t data_age = 0) {
generate_pair(serial, key, data, data_age);
}
bool mode_readonly() const {
return (config.params.mode_flags & MDB_RDONLY) ? true : false;
}

View File

@ -14,7 +14,7 @@
#include "test.h"
#include <float.h>
#ifndef _MSC_VER
#ifdef HAVE_IEEE754_H
#include <ieee754.h>
#endif
@ -190,14 +190,11 @@ uint64_t entropy_ticks(void) {
//-----------------------------------------------------------------------------
static __inline uint64_t bleach64(uint64_t dirty) {
dirty = mul_64x64_high(bswap64(dirty), UINT64_C(17048867929148541611));
return dirty;
return mul_64x64_high(bswap64(dirty), UINT64_C(17048867929148541611));
}
static __inline uint32_t bleach32(uint32_t dirty) {
return (uint32_t)(
(bswap32(dirty) * UINT64_C(/*3080105489, 4267077937 */ 2175734609)) >>
32);
return (uint32_t)((bswap32(dirty) * UINT64_C(2175734609)) >> 32);
}
uint64_t prng64_careless(uint64_t &state) {
@ -214,6 +211,39 @@ uint32_t prng32(uint64_t &state) {
return (uint32_t)(prng64_careless(state) >> 32);
}
void prng_fill(uint64_t &state, void *ptr, size_t bytes) {
while (bytes >= 4) {
*((uint32_t *)ptr) = prng32(state);
ptr = (uint32_t *)ptr + 1;
bytes -= 4;
}
switch (bytes & 3) {
case 3: {
uint32_t u32 = prng32(state);
memcpy(ptr, &u32, 3);
} break;
case 2:
*((uint16_t *)ptr) = (uint16_t)prng32(state);
break;
case 1:
*((uint8_t *)ptr) = (uint8_t)prng32(state);
break;
case 0:
break;
}
}
static __thread uint64_t prng_state;
void prng_seed(uint64_t seed) { prng_state = bleach64(seed); }
uint32_t prng32(void) { return prng32(prng_state); }
uint64_t prng64(void) { return prng64_white(prng_state); }
void prng_fill(void *ptr, size_t bytes) { prng_fill(prng_state, ptr, bytes); }
uint64_t entropy_white() { return bleach64(entropy_ticks()); }
double double_from_lower(uint64_t salt) {

View File

@ -101,12 +101,12 @@
#define bswap64(v) __bswap_64(v)
#else
static __inline uint64_t bswap64(uint64_t v) {
return v << 56 | v >> 56 | ((v << 40) & 0x00ff000000000000ull) |
((v << 24) & 0x0000ff0000000000ull) |
((v << 8) & 0x000000ff00000000ull) |
((v >> 8) & 0x00000000ff000000ull) |
((v >> 24) & 0x0000000000ff0000ull) |
((v >> 40) & 0x000000000000ff00ull);
return v << 56 | v >> 56 | ((v << 40) & UINT64_C(0x00ff000000000000)) |
((v << 24) & UINT64_C(0x0000ff0000000000)) |
((v << 8) & UINT64_C(0x000000ff00000000)) |
((v >> 8) & UINT64_C(0x00000000ff0000000)) |
((v >> 24) & UINT64_C(0x0000000000ff0000)) |
((v >> 40) & UINT64_C(0x000000000000ff00));
}
#endif
#endif /* bswap64 */
@ -116,7 +116,8 @@ static __inline uint64_t bswap64(uint64_t v) {
#define bswap32(v) __bswap_32(v)
#else
static __inline uint32_t bswap32(uint32_t v) {
return v << 24 | v >> 24 | ((v << 8) & 0x00ff0000) | ((v >> 8) & 0x0000ff00);
return v << 24 | v >> 24 | ((v << 8) & UINT32_C(0x00ff0000)) |
((v >> 8) & UINT32_C(0x0000ff00));
}
#endif
#endif /* bswap32 */
@ -360,6 +361,12 @@ uint64_t entropy_white(void);
uint64_t prng64_careless(uint64_t &state);
uint64_t prng64_white(uint64_t &state);
uint32_t prng32(uint64_t &state);
void prng_fill(uint64_t &state, void *ptr, size_t bytes);
void prng_seed(uint64_t seed);
uint32_t prng32(void);
uint64_t prng64(void);
void prng_fill(void *ptr, size_t bytes);
bool flipcoin();
bool jitter(unsigned probability_percent);