diff options
Diffstat (limited to 'plugins/Dbx_mdbx/src/libmdbx/test')
23 files changed, 4902 insertions, 0 deletions
diff --git a/plugins/Dbx_mdbx/src/libmdbx/test/base.h b/plugins/Dbx_mdbx/src/libmdbx/test/base.h new file mode 100644 index 0000000000..ffcf61adf3 --- /dev/null +++ b/plugins/Dbx_mdbx/src/libmdbx/test/base.h @@ -0,0 +1,113 @@ +/* + * Copyright 2017 Leonid Yuriev <leo@yuriev.ru> + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#pragma once + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#if defined(_WIN32) || defined(_WIN64) || defined(_WINDOWS) +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS +#pragma warning(push, 1) +#pragma warning(disable : 4548) /* expression before comma has no effect; \ + expected expression with side - effect */ +#pragma warning(disable : 4530) /* C++ exception handler used, but unwind \ + semantics are not enabled. Specify /EHsc */ +#pragma warning(disable : 4577) /* 'noexcept' used with no exception handling \ + mode specified; termination on exception \ + is not guaranteed. Specify /EHsc */ +#endif /* _MSC_VER (warnings) */ + +/* If you wish to build your application for a previous Windows platform, +* include WinSDKVer.h and set the _WIN32_WINNT macro to the platform you +* wish to support before including SDKDDKVer.h. +* +* TODO: #define _WIN32_WINNT WIN32_MUSTDIE */ +#include <SDKDDKVer.h> +#endif /* WINDOWS */ + +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#if defined(_WIN32) || defined(_WIN64) || defined(_WINDOWS) +#include <io.h> +#else +#include <fcntl.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> +#endif + +#ifdef _BSD_SOURCE +#include <endian.h> +#endif + +#include <algorithm> +#include <cassert> +#include <cinttypes> // for PRId64, PRIu64 +#include <cstdarg> +#include <cstddef> +#include <cstdint> +#include <map> +#include <memory> +#include <set> +#include <string> +#include <type_traits> +#include <unordered_map> +#include <unordered_set> +#include <vector> + +#ifdef _MSC_VER +#include <intrin.h> +#endif + +#if defined(__i386__) || defined(__x86_64__) +#include <x86intrin.h> +#endif + +#include "../mdbx.h" +#include "../src/defs.h" +#include "../src/osal.h" + +#ifdef _MSC_VER +#pragma warning(pop) +#pragma warning(disable : 4201) /* nonstandard extension used : \ + nameless struct / union */ +#pragma warning(disable : 4127) /* conditional expression is constant */ +#if _MSC_VER < 1900 +#pragma warning(disable : 4510) /* default constructor could \ + not be generated */ +#pragma warning(disable : 4512) /* assignment operator could \ + not be generated */ +#pragma warning(disable : 4610) /* user-defined constructor required */ +#ifndef snprintf +#define snprintf(buffer, buffer_size, format, ...) \ + _snprintf_s(buffer, buffer_size, _TRUNCATE, format, __VA_ARGS__) +#endif +#ifndef vsnprintf +#define vsnprintf(buffer, buffer_size, format, args) \ + _vsnprintf_s(buffer, buffer_size, _TRUNCATE, format, args) +#endif +#pragma warning(disable : 4996) /* 'vsnprintf': This function or variable \ + may be unsafe */ +#endif +#endif /* _MSC_VER */ diff --git a/plugins/Dbx_mdbx/src/libmdbx/test/cases.cc b/plugins/Dbx_mdbx/src/libmdbx/test/cases.cc new file mode 100644 index 0000000000..a1953f53c1 --- /dev/null +++ b/plugins/Dbx_mdbx/src/libmdbx/test/cases.cc @@ -0,0 +1,97 @@ +/* + * Copyright 2017 Leonid Yuriev <leo@yuriev.ru> + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "test.h" + +void configure_actor(unsigned &last_space_id, const actor_testcase testcase, + const char *space_id_cstr, const actor_params ¶ms) { + unsigned wait4id = 0; + + if (params.waitfor_nops) { + for (auto i = global::actors.rbegin(); i != global::actors.rend(); ++i) { + if (i->is_waitable(params.waitfor_nops)) { + if (i->signal_nops && i->signal_nops != params.waitfor_nops) + failure("Previous waitable actor (id=%u) already linked on %u-ops\n", + i->actor_id, i->signal_nops); + wait4id = i->actor_id; + i->signal_nops = params.waitfor_nops; + break; + } + } + if (!wait4id) + failure("No previous waitable actor for %u-ops\n", params.waitfor_nops); + } + + 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; + space_id = strtoul(space_id_cstr, &end, 0); + if (errno) + failure_perror("Expects an integer value for space-id\n", errno); + if (end && *end) + failure("The '%s' is unexpected for space-id\n", end); + } + + if (space_id > ACTOR_ID_MAX) + failure("Invalid space-id %u\n", space_id); + last_space_id = space_id; + + 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 ¶ms, + unsigned &last_space_id) { + if (strcmp(casename, "basic") == 0) { + log_notice(">>> testcase_setup(%s)", casename); + 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); + configure_actor(last_space_id, ac_try, nullptr, params); + log_notice("<<< testcase_setup(%s): done", casename); + } else { + failure("unknown testcase `%s`", casename); + } +} + +void keycase_setup(const char *casename, actor_params ¶ms) { + 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 */ diff --git a/plugins/Dbx_mdbx/src/libmdbx/test/chrono.cc b/plugins/Dbx_mdbx/src/libmdbx/test/chrono.cc new file mode 100644 index 0000000000..b6245295e7 --- /dev/null +++ b/plugins/Dbx_mdbx/src/libmdbx/test/chrono.cc @@ -0,0 +1,129 @@ +/* + * Copyright 2017 Leonid Yuriev <leo@yuriev.ru> + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "test.h" + +namespace chrono { + +#define NSEC_PER_SEC 1000000000u +uint32_t ns2fractional(uint32_t ns) { + assert(ns < NSEC_PER_SEC); + /* LY: здесь и далее используется "длинное деление", которое + * для ясности кода оставлено как есть (без ручной оптимизации). Так как + * GCC, Clang и даже MSVC сами давно умеют конвертировать деление на + * константу в быструю reciprocal-форму. */ + return ((uint64_t)ns << 32) / NSEC_PER_SEC; +} + +uint32_t fractional2ns(uint32_t fractional) { + return (fractional * (uint64_t)NSEC_PER_SEC) >> 32; +} + +#define USEC_PER_SEC 1000000u +uint32_t us2fractional(uint32_t us) { + assert(us < USEC_PER_SEC); + return ((uint64_t)us << 32) / USEC_PER_SEC; +} + +uint32_t fractional2us(uint32_t fractional) { + return (fractional * (uint64_t)USEC_PER_SEC) >> 32; +} + +#define MSEC_PER_SEC 1000u +uint32_t ms2fractional(uint32_t ms) { + assert(ms < MSEC_PER_SEC); + return ((uint64_t)ms << 32) / MSEC_PER_SEC; +} + +uint32_t fractional2ms(uint32_t fractional) { + return (fractional * (uint64_t)MSEC_PER_SEC) >> 32; +} + +time from_ns(uint64_t ns) { + time result; + result.fixedpoint = ((ns / NSEC_PER_SEC) << 32) | + ns2fractional((uint32_t)(ns % NSEC_PER_SEC)); + return result; +} + +time from_us(uint64_t us) { + time result; + result.fixedpoint = ((us / USEC_PER_SEC) << 32) | + us2fractional((uint32_t)(us % USEC_PER_SEC)); + return result; +} + +time from_ms(uint64_t ms) { + time result; + result.fixedpoint = ((ms / MSEC_PER_SEC) << 32) | + ms2fractional((uint32_t)(ms % MSEC_PER_SEC)); + return result; +} + +time now_realtime() { +#if defined(_WIN32) || defined(_WIN64) || defined(_WINDOWS) + static void(WINAPI * query_time)(LPFILETIME); + if (!query_time) { + query_time = (void(WINAPI *)(LPFILETIME))GetProcAddress( + GetModuleHandle(TEXT("kernel32.dll")), + "GetSystemTimePreciseAsFileTime"); + if (!query_time) + query_time = GetSystemTimeAsFileTime; + } + + FILETIME filetime; + query_time(&filetime); + uint64_t ns100 = + (uint64_t)filetime.dwHighDateTime << 32 | filetime.dwLowDateTime; + return from_ns((ns100 - UINT64_C(116444736000000000)) * 100u); +#else + struct timespec ts; + if (unlikely(clock_gettime(CLOCK_REALTIME, &ts))) + failure_perror("clock_gettime(CLOCK_REALTIME", errno); + + return from_timespec(ts); +#endif +} + +time now_motonic() { +#if defined(_WIN32) || defined(_WIN64) || defined(_WINDOWS) + static uint64_t reciprocal; + static LARGE_INTEGER Frequency; + if (reciprocal == 0) { + if (!QueryPerformanceFrequency(&Frequency)) + failure_perror("QueryPerformanceFrequency()", GetLastError()); + reciprocal = (((UINT64_C(1) << 48) + Frequency.QuadPart / 2 + 1) / + Frequency.QuadPart); + assert(reciprocal); + } + + LARGE_INTEGER Counter; + if (!QueryPerformanceCounter(&Counter)) + failure_perror("QueryPerformanceCounter()", GetLastError()); + + time result; + result.fixedpoint = (Counter.QuadPart / Frequency.QuadPart) << 32; + uint64_t mod = Counter.QuadPart % Frequency.QuadPart; + result.fixedpoint += (mod * reciprocal) >> 16; + return result; +#else + struct timespec ts; + if (unlikely(clock_gettime(CLOCK_MONOTONIC, &ts))) + failure_perror("clock_gettime(CLOCK_MONOTONIC)", errno); + + return from_timespec(ts); +#endif +} + +} /* namespace chrono */ diff --git a/plugins/Dbx_mdbx/src/libmdbx/test/chrono.h b/plugins/Dbx_mdbx/src/libmdbx/test/chrono.h new file mode 100644 index 0000000000..d5e9f658d1 --- /dev/null +++ b/plugins/Dbx_mdbx/src/libmdbx/test/chrono.h @@ -0,0 +1,100 @@ +/* + * Copyright 2017 Leonid Yuriev <leo@yuriev.ru> + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#pragma once + +#include "base.h" +#include "log.h" +#include "utils.h" + +namespace chrono { + +#pragma pack(push, 1) + +typedef union time { + uint64_t fixedpoint; + struct { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + uint32_t fractional; + union { + uint32_t utc; + uint32_t integer; + }; +#else + union { + uint32_t utc; + uint32_t integer; + }; + uint32_t fractional; +#endif + }; + + void reset() { fixedpoint = 0; } + uint32_t seconds() const { return utc; } +} time; + +#pragma pack(pop) + +uint32_t ns2fractional(uint32_t); +uint32_t fractional2ns(uint32_t); +uint32_t us2fractional(uint32_t); +uint32_t fractional2us(uint32_t); +uint32_t ms2fractional(uint32_t); +uint32_t fractional2ms(uint32_t); + +time from_ns(uint64_t us); +time from_us(uint64_t ns); +time from_ms(uint64_t ms); + +inline time from_seconds(uint64_t seconds) { + assert(seconds < UINT32_MAX); + time result; + result.fixedpoint = seconds << 32; + return result; +} + +inline time from_utc(time_t utc) { + assert(utc >= 0); + return from_seconds((uint64_t)utc); +} + +inline time infinite() { + time result; + result.fixedpoint = UINT64_MAX; + return result; +} + +#if defined(HAVE_TIMESPEC_TV_NSEC) || defined(__timespec_defined) || \ + defined(CLOCK_REALTIME) +inline time from_timespec(const struct timespec &ts) { + time result; + result.fixedpoint = + ((uint64_t)ts.tv_sec << 32) | ns2fractional((uint32_t)ts.tv_nsec); + return result; +} +#endif /* HAVE_TIMESPEC_TV_NSEC */ + +#if defined(HAVE_TIMEVAL_TV_USEC) || defined(_STRUCT_TIMEVAL) +inline time from_timeval(const struct timeval &tv) { + time result; + result.fixedpoint = + ((uint64_t)tv.tv_sec << 32) | us2fractional((uint32_t)tv.tv_usec); + return result; +} +#endif /* HAVE_TIMEVAL_TV_USEC */ + +time now_realtime(); +time now_motonic(); + +} /* namespace chrono */ diff --git a/plugins/Dbx_mdbx/src/libmdbx/test/config.cc b/plugins/Dbx_mdbx/src/libmdbx/test/config.cc new file mode 100644 index 0000000000..81b26b8383 --- /dev/null +++ b/plugins/Dbx_mdbx/src/libmdbx/test/config.cc @@ -0,0 +1,468 @@ +/* + * Copyright 2017 Leonid Yuriev <leo@yuriev.ru> + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "test.h" + +#if defined(_MSC_VER) && !defined(strcasecmp) +#define strcasecmp(str, len) _stricmp(str, len) +#endif /* _MSC_VER && strcasecmp() */ + +namespace config { + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + const char **value, const char *default_value) { + assert(narg < argc); + const char *current = argv[narg]; + const size_t optlen = strlen(option); + + if (strncmp(current, "--", 2) || strncmp(current + 2, option, optlen)) + return false; + + if (!value) { + if (current[optlen + 2] == '=') + failure("Option '--%s' doen't accept any value\n", option); + return true; + } + + *value = nullptr; + if (current[optlen + 2] == '=') { + *value = ¤t[optlen + 3]; + return true; + } + + if (narg + 1 < argc && strncmp("--", argv[narg + 1], 2) != 0) { + *value = argv[narg + 1]; + ++narg; + return true; + } + + if (default_value) { + *value = default_value; + return true; + } + + failure("No value given for '--%s' option\n", option); +} + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + std::string &value, bool allow_empty) { + const char *value_cstr; + if (!parse_option(argc, argv, narg, option, &value_cstr, + allow_empty ? "" : nullptr)) + return false; + + if (!allow_empty && strlen(value_cstr) == 0) + failure("Value for option '--%s' could't be empty\n", option); + + value = value_cstr; + return true; +} + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + unsigned &mask, const option_verb *verbs) { + const char *list; + if (!parse_option(argc, argv, narg, option, &list)) + return false; + + mask = 0; + while (*list) { + if (*list == ',' || *list == ' ' || *list == '\t') { + ++list; + continue; + } + + const char *const comma = strchr(list, ','); + const size_t len = (comma) ? comma - list : strlen(list); + const option_verb *scan = verbs; + while (true) { + if (!scan->verb) + failure("Unknown verb '%.*s', for option '==%s'\n", (int)len, list, + option); + if (strlen(scan->verb) == len && strncmp(list, scan->verb, len) == 0) { + mask |= scan->mask; + list += len; + break; + } + ++scan; + } + } + + return true; +} + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + uint64_t &value, const scale_mode scale, + const uint64_t minval, const uint64_t maxval) { + + const char *value_cstr; + if (!parse_option(argc, argv, narg, option, &value_cstr)) + return false; + + char *suffix = nullptr; + errno = 0; + unsigned long raw = strtoul(value_cstr, &suffix, 0); + if (errno) + failure("Option '--%s' expects a numeric value (%s)\n", option, + test_strerror(errno)); + + uint64_t multipler = 1; + if (suffix && *suffix) { + if (scale == no_scale) + failure("Option '--%s' doen't accepts suffixes, so '%s' is unexpected\n", + option, suffix); + if (strcmp(suffix, "K") == 0 || strcasecmp(suffix, "Kilo") == 0) + multipler = (scale == decimal) ? UINT64_C(1000) : UINT64_C(1024); + else if (strcmp(suffix, "M") == 0 || strcasecmp(suffix, "Mega") == 0) + multipler = + (scale == decimal) ? UINT64_C(1000) * 1000 : UINT64_C(1024) * 1024; + else if (strcmp(suffix, "G") == 0 || strcasecmp(suffix, "Giga") == 0) + multipler = (scale == decimal) ? UINT64_C(1000) * 1000 * 1000 + : UINT64_C(1024) * 1024 * 1024; + else if (strcmp(suffix, "T") == 0 || strcasecmp(suffix, "Tera") == 0) + multipler = (scale == decimal) ? UINT64_C(1000) * 1000 * 1000 * 1000 + : UINT64_C(1024) * 1024 * 1024 * 1024; + else if (scale == duration && + (strcmp(suffix, "s") == 0 || strcasecmp(suffix, "Seconds") == 0)) + multipler = 1; + else if (scale == duration && + (strcmp(suffix, "m") == 0 || strcasecmp(suffix, "Minutes") == 0)) + multipler = 60; + else if (scale == duration && + (strcmp(suffix, "h") == 0 || strcasecmp(suffix, "Hours") == 0)) + multipler = 3600; + else if (scale == duration && + (strcmp(suffix, "d") == 0 || strcasecmp(suffix, "Days") == 0)) + multipler = 3600 * 24; + else + failure( + "Option '--%s' expects a numeric value with Kilo/Mega/Giga/Tera %s" + "suffixes, but '%s' is unexpected\n", + option, (scale == duration) ? "or Seconds/Minutes/Hours/Days " : "", + suffix); + } + + if (raw >= UINT64_MAX / multipler) + failure("The value for option '--%s' is too huge\n", option); + + value = raw * multipler; + if (maxval && value > maxval) + failure("The maximal value for option '--%s' is %" PRIu64 "\n", option, + maxval); + if (value < minval) + failure("The minimal value for option '--%s' is %" PRIu64 "\n", option, + minval); + return true; +} + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + unsigned &value, const scale_mode scale, + const unsigned minval, const unsigned maxval) { + + uint64_t huge; + if (!parse_option(argc, argv, narg, option, huge, scale, minval, maxval)) + return false; + value = (unsigned)huge; + 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; + if (!parse_option(argc, argv, narg, option, &value_cstr, "yes")) { + const char *current = argv[narg]; + if (strncmp(current, "--no-", 5) == 0 && strcmp(current + 5, option) == 0) { + value = false; + return true; + } + if (strncmp(current, "--dont-", 7) == 0 && + strcmp(current + 7, option) == 0) { + value = false; + return true; + } + return false; + } + + if (!value_cstr) { + value = true; + return true; + } + + if (strcasecmp(value_cstr, "yes") == 0 || strcasecmp(value_cstr, "1") == 0) { + value = true; + return true; + } + + if (strcasecmp(value_cstr, "no") == 0 || strcasecmp(value_cstr, "0") == 0) { + value = false; + return true; + } + + failure( + "Option '--%s' expects a 'boolean' value Yes/No, so '%s' is unexpected\n", + option, value_cstr); +} + +//----------------------------------------------------------------------------- + +const struct option_verb mode_bits[] = { + {"rdonly", MDBX_RDONLY}, {"mapasync", MDBX_MAPASYNC}, + {"utterly", MDBX_UTTERLY_NOSYNC}, {"nosubdir", MDBX_NOSUBDIR}, + {"nosync", MDBX_NOSYNC}, {"nometasync", MDBX_NOMETASYNC}, + {"writemap", MDBX_WRITEMAP}, {"notls", MDBX_NOTLS}, + {"nordahead", MDBX_NORDAHEAD}, {"nomeminit", MDBX_NOMEMINIT}, + {"coalesce", MDBX_COALESCE}, {"lifo", MDBX_LIFORECLAIM}, + {"perturb", MDBX_PAGEPERTURB}, {nullptr, 0}}; + +const struct option_verb table_bits[] = { + {"key.reverse", MDBX_REVERSEKEY}, + {"key.integer", MDBX_INTEGERKEY}, + {"data.integer", MDBX_INTEGERDUP | MDBX_DUPFIXED | MDBX_DUPSORT}, + {"data.fixed", MDBX_DUPFIXED | MDBX_DUPSORT}, + {"data.reverse", MDBX_REVERSEDUP | MDBX_DUPSORT}, + {"data.dups", MDBX_DUPSORT}, + {nullptr, 0}}; + +static void dump_verbs(const char *caption, size_t bits, + const struct option_verb *verbs) { + log_info("%s: 0x%" PRIx64 " = ", caption, (uint64_t)bits); + + const char *comma = ""; + while (verbs->mask && bits) { + if ((bits & verbs->mask) == verbs->mask) { + logging::feed("%s%s", comma, verbs->verb); + bits -= verbs->mask; + comma = ", "; + } + ++verbs; + } + + logging::feed("\n"); +} + +static void dump_duration(const char *caption, unsigned duration) { + log_info("%s: ", caption); + if (duration) { + if (duration > 24 * 3600) + logging::feed("%u_", duration / (24 * 3600)); + if (duration > 3600) + logging::feed("%02u:", (duration % (24 * 3600)) / 3600); + logging::feed("%02u:%02u", (duration % 3600) / 60, duration % 60); + } else { + logging::feed("INFINITE"); + } + logging::feed("\n"); +} + +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(); + + if (i->params.loglevel) { + log_info("log: level %u, %s\n", i->params.loglevel, + i->params.pathname_log.empty() ? "console" + : i->params.pathname_log.c_str()); + } + + log_info("database: %s, size %" PRIu64 "\n", i->params.pathname_db.c_str(), + i->params.size); + + dump_verbs("mode", i->params.mode_flags, mode_bits); + dump_verbs("table", i->params.table_flags, table_bits); + + if (i->params.test_nops) + log_info("iterations/records %u\n", i->params.test_nops); + else + dump_duration("duration", i->params.test_duration); + + if (i->params.nrepeat) + log_info("repeat %u\n", i->params.nrepeat); + else + log_info("repeat ETERNALLY\n"); + + 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, + i->params.datalen_max); + + log_info("batch: read %u, write %u\n", i->params.batch_read, + i->params.batch_write); + + if (i->params.waitfor_nops) + log_info("wait: actor %u for %u ops\n", i->wait4id, + i->params.waitfor_nops); + else if (i->params.delaystart) + dump_duration("delay", i->params.delaystart); + else + log_info("no-delay\n"); + + log_info("limits: readers %u, tables %u\n", i->params.max_readers, + i->params.max_tables); + + log_info("drop table: %s\n", i->params.drop_table ? "Yes" : "No"); + indent.pop(); + } + + dump_duration("timeout", global::config::timeout_duration_seconds); + log_info("cleanup: before %s, after %s\n", + global::config::cleanup_before ? "Yes" : "No", + global::config::cleanup_after ? "Yes" : "No"); + + log_info("failfast: %s\n", global::config::failfast ? "Yes" : "No"); + log_info("progress indicator: %s\n", + global::config::progress_indicator ? "Yes" : "No"); +} + +} /* namespace config */ + +//----------------------------------------------------------------------------- + +using namespace config; + +actor_config::actor_config(actor_testcase testcase, const actor_params ¶ms, + unsigned space_id, unsigned wait4id) + : params(params) { + this->space_id = space_id; + this->actor_id = 1 + (unsigned)global::actors.size(); + this->testcase = testcase; + this->wait4id = wait4id; + signal_nops = 0; +} + +const std::string actor_config::serialize(const char *prefix) const { + simple_checksum checksum; + + std::string result; + if (prefix) + result.append(prefix); + + checksum.push(params.pathname_db); + result.append(params.pathname_db); + result.append("|"); + + checksum.push(params.pathname_log); + result.append(params.pathname_log); + result.append("|"); + + static_assert(std::is_pod<actor_params_pod>::value, + "actor_params_pod should by POD"); + result.append(data2hex(static_cast<const actor_params_pod *>(¶ms), + sizeof(actor_params_pod), checksum)); + result.append("|"); + + static_assert(std::is_pod<actor_config_pod>::value, + "actor_config_pod should by POD"); + result.append(data2hex(static_cast<const actor_config_pod *>(this), + sizeof(actor_config_pod), checksum)); + result.append("|"); + + result.append(osal_serialize(checksum)); + result.append("|"); + + result.append(std::to_string(checksum.value)); + return result; +} + +bool actor_config::deserialize(const char *str, actor_config &config) { + simple_checksum checksum; + + TRACE(">> actor_config::deserialize: %s\n", str); + + const char *slash = strchr(str, '|'); + if (!slash) { + TRACE("<< actor_config::deserialize: slash-1\n"); + return false; + } + config.params.pathname_db.assign(str, slash - str); + checksum.push(config.params.pathname_db); + str = slash + 1; + + slash = strchr(str, '|'); + if (!slash) { + TRACE("<< actor_config::deserialize: slash-2\n"); + return false; + } + config.params.pathname_log.assign(str, slash - str); + checksum.push(config.params.pathname_log); + str = slash + 1; + + slash = strchr(str, '|'); + if (!slash) { + TRACE("<< actor_config::deserialize: slash-3\n"); + return false; + } + static_assert(std::is_pod<actor_params_pod>::value, + "actor_params_pod should by POD"); + if (!hex2data(str, slash, static_cast<actor_params_pod *>(&config.params), + sizeof(actor_params_pod), checksum)) { + TRACE("<< actor_config::deserialize: actor_params_pod(%.*s)\n", + (int)(slash - str), str); + return false; + } + str = slash + 1; + + slash = strchr(str, '|'); + if (!slash) { + TRACE("<< actor_config::deserialize: slash-4\n"); + return false; + } + static_assert(std::is_pod<actor_config_pod>::value, + "actor_config_pod should by POD"); + if (!hex2data(str, slash, static_cast<actor_config_pod *>(&config), + sizeof(actor_config_pod), checksum)) { + TRACE("<< actor_config::deserialize: actor_config_pod(%.*s)\n", + (int)(slash - str), str); + return false; + } + str = slash + 1; + + slash = strchr(str, '|'); + if (!slash) { + TRACE("<< actor_config::deserialize: slash-5\n"); + return false; + } + if (!config.osal_deserialize(str, slash, checksum)) { + TRACE("<< actor_config::deserialize: osal\n"); + return false; + } + str = slash + 1; + + uint64_t verify = std::stoull(std::string(str)); + if (checksum.value != verify) { + TRACE("<< actor_config::deserialize: checksum mismatch\n"); + return false; + } + + TRACE("<< actor_config::deserialize: OK\n"); + return true; +} diff --git a/plugins/Dbx_mdbx/src/libmdbx/test/config.h b/plugins/Dbx_mdbx/src/libmdbx/test/config.h new file mode 100644 index 0000000000..4c9aa2142b --- /dev/null +++ b/plugins/Dbx_mdbx/src/libmdbx/test/config.h @@ -0,0 +1,279 @@ +/* + * Copyright 2017 Leonid Yuriev <leo@yuriev.ru> + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#pragma once + +#include "base.h" +#include "log.h" +#include "utils.h" + +#define ACTOR_ID_MAX INT16_MAX + +enum actor_testcase { + ac_none, + ac_hill, + ac_deadread, + ac_deadwrite, + ac_jitter, + ac_try +}; + +enum actor_status { + as_unknown, + as_debuging, + as_running, + as_successful, + as_killed, + as_failed +}; + +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 { + +enum scale_mode { no_scale, decimal, binary, duration }; + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + const char **value, const char *default_value = nullptr); + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + std::string &value, bool allow_empty = false); + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + bool &value); + +struct option_verb { + const char *const verb; + unsigned mask; +}; + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + unsigned &mask, const option_verb *verbs); + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + uint64_t &value, const scale_mode scale, + const uint64_t minval = 0, const uint64_t maxval = INT64_MAX); + +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; + + unsigned mode_flags; + unsigned table_flags; + uint64_t size; + + unsigned test_duration; + unsigned test_nops; + unsigned nrepeat; + unsigned nthreads; + + unsigned keylen_min, keylen_max; + unsigned datalen_min, datalen_max; + + unsigned batch_read; + unsigned batch_write; + + unsigned delaystart; + unsigned waitfor_nops; + + unsigned max_readers; + unsigned max_tables; + keygen_params_pod keygen; + + bool drop_table; +}; + +struct actor_config_pod { + unsigned actor_id, space_id; + actor_testcase testcase; + unsigned wait4id; + unsigned signal_nops; +}; + +#pragma pack(pop) + +extern const struct option_verb mode_bits[]; +extern const struct option_verb table_bits[]; +void dump(const char *title = "config-dump: "); + +} /* namespace config */ + +struct actor_params : public config::actor_params_pod { + std::string pathname_log; + std::string pathname_db; + void set_defaults(void); +}; + +struct actor_config : public config::actor_config_pod { + actor_params params; + + bool wanna_event4signalling() const { return true /* TODO ? */; } + + actor_config(actor_testcase testcase, const actor_params ¶ms, + unsigned space_id, unsigned wait4id); + + actor_config(const char *str) { + if (!deserialize(str, *this)) + failure("Invalid internal parameter '%s'\n", str); + } + + const std::string osal_serialize(simple_checksum &) const; + bool osal_deserialize(const char *str, const char *end, simple_checksum &); + + const std::string serialize(const char *prefix) const; + static bool deserialize(const char *str, actor_config &config); + + bool is_waitable(size_t nops) const { + switch (testcase) { + case ac_hill: + if (!params.test_nops || params.test_nops >= nops) + return true; + __fallthrough; + default: + return false; + } + } +}; diff --git a/plugins/Dbx_mdbx/src/libmdbx/test/dead.cc b/plugins/Dbx_mdbx/src/libmdbx/test/dead.cc new file mode 100644 index 0000000000..c7b338ff7a --- /dev/null +++ b/plugins/Dbx_mdbx/src/libmdbx/test/dead.cc @@ -0,0 +1,63 @@ +/* + * Copyright 2017 Leonid Yuriev <leo@yuriev.ru> + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "test.h" + +bool testcase_deadread::setup() { + log_trace(">> setup"); + if (!inherited::setup()) + return false; + + log_trace("<< setup"); + return true; +} + +bool testcase_deadread::run() { + db_open(); + txn_begin(true); + return true; +} + +bool testcase_deadread::teardown() { + log_trace(">> teardown"); + cursor_guard.release(); + txn_guard.release(); + db_guard.release(); + return inherited::teardown(); +} + +//----------------------------------------------------------------------------- + +bool testcase_deadwrite::setup() { + log_trace(">> setup"); + if (!inherited::setup()) + return false; + + log_trace("<< setup"); + return true; +} + +bool testcase_deadwrite::run() { + db_open(); + txn_begin(false); + return true; +} + +bool testcase_deadwrite::teardown() { + log_trace(">> teardown"); + cursor_guard.release(); + txn_guard.release(); + db_guard.release(); + return inherited::teardown(); +} diff --git a/plugins/Dbx_mdbx/src/libmdbx/test/hill.cc b/plugins/Dbx_mdbx/src/libmdbx/test/hill.cc new file mode 100644 index 0000000000..72a6e95b35 --- /dev/null +++ b/plugins/Dbx_mdbx/src/libmdbx/test/hill.cc @@ -0,0 +1,227 @@ +/* + * Copyright 2017 Leonid Yuriev <leo@yuriev.ru> + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "test.h" + +bool testcase_hill::setup() { + log_trace(">> setup"); + if (!inherited::setup()) + return false; + + /* TODO */ + + log_trace("<< setup"); + return true; +} + +bool testcase_hill::run() { + db_open(); + + txn_begin(false); + MDBX_dbi dbi = db_table_open(true); + txn_end(false); + + /* LY: тест "холмиком": + * - сначала наполняем таблицу циклическими CRUD-манипуляциями, + * которые в каждом цикле делают несколько операций, включая удаление, + * но в результате добавляют записи. + * - затем очищаем таблицу также CRUD-манипуляциями, но уже с другой + * пропорцией удалений. + * + * При этом очень многое зависит от порядка перебора ключей: + * - (псевдо)случайное распределение требуется лишь для полноты картины, + * но в целом не покрывает важных кейсов. + * - кроме (псевдо)случайного перебора требуется последовательное + * итерирование ключей интервалами различной ширины, с тем чтобы + * проверить различные варианты как разделения, так и слияния страниц + * внутри движка. + * - при не-уникальных ключах (MDBX_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 & MDBX_DUPSORT) + ? MDBX_NODUPDATA + : MDBX_NODUPDATA | MDBX_NOOVERWRITE; + const unsigned update_flags = + MDBX_CURRENT | MDBX_NODUPDATA | MDBX_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 != MDBX_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 != MDBX_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 != MDBX_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 != MDBX_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 != MDBX_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 != MDBX_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 != MDBX_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 != MDBX_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; +} + +bool testcase_hill::teardown() { + log_trace(">> teardown"); + return inherited::teardown(); +} diff --git a/plugins/Dbx_mdbx/src/libmdbx/test/jitter.cc b/plugins/Dbx_mdbx/src/libmdbx/test/jitter.cc new file mode 100644 index 0000000000..92d272e1e5 --- /dev/null +++ b/plugins/Dbx_mdbx/src/libmdbx/test/jitter.cc @@ -0,0 +1,69 @@ +/* + * Copyright 2017 Leonid Yuriev <leo@yuriev.ru> + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "test.h" + +bool testcase_jitter::setup() { + log_trace(">> setup"); + if (!inherited::setup()) + return false; + + log_trace("<< setup"); + return true; +} + +bool testcase_jitter::run() { + while (should_continue()) { + jitter_delay(); + db_open(); + + if (flipcoin()) { + jitter_delay(); + txn_begin(true); + fetch_canary(); + jitter_delay(); + txn_end(flipcoin()); + } + + jitter_delay(); + txn_begin(mode_readonly()); + jitter_delay(); + if (!mode_readonly()) { + fetch_canary(); + update_canary(1); + /* TODO: + * - db_setsize() + * ... + */ + } + txn_end(flipcoin()); + + if (flipcoin()) { + jitter_delay(); + txn_begin(true); + jitter_delay(); + txn_end(flipcoin()); + } + + jitter_delay(); + db_close(); + report(1); + } + return true; +} + +bool testcase_jitter::teardown() { + log_trace(">> teardown"); + return inherited::teardown(); +} diff --git a/plugins/Dbx_mdbx/src/libmdbx/test/keygen.cc b/plugins/Dbx_mdbx/src/libmdbx/test/keygen.cc new file mode 100644 index 0000000000..806d4ba80d --- /dev/null +++ b/plugins/Dbx_mdbx/src/libmdbx/test/keygen.cc @@ -0,0 +1,238 @@ +/* + * Copyright 2017 Leonid Yuriev <leo@yuriev.ru> + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "test.h" + +namespace keygen { + +static inline __pure_function serial_t mask(unsigned bits) { + assert(bits > 0 && bits <= serial_maxwith); + return serial_allones >> (serial_maxwith - bits); +} + +/* 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 & (MDBX_INTEGERDUP | MDBX_REVERSEDUP))); + assert(!(value_essentials.flags & (MDBX_INTEGERKEY | MDBX_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 & (MDBX_INTEGERKEY | MDBX_REVERSEKEY); + assert(actor.keylen_min < UINT8_MAX); + key_essentials.minlen = (uint8_t)actor.keylen_min; + assert(actor.keylen_max < UINT16_MAX); + key_essentials.maxlen = (uint16_t)actor.keylen_max; + + value_essentials.flags = + actor.table_flags & (MDBX_INTEGERDUP | MDBX_REVERSEDUP); + assert(actor.datalen_min < UINT8_MAX); + value_essentials.minlen = (uint8_t)actor.datalen_min; + assert(actor.datalen_max < UINT16_MAX); + value_essentials.maxlen = (uint16_t)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 ¶ms, + result &out) { + assert(out.limit >= params.maxlen); + assert(params.maxlen >= params.minlen); + assert(params.maxlen >= length(serial)); + + out.value.iov_base = out.bytes; + out.value.iov_len = params.minlen; + + if (params.flags & (MDBX_INTEGERKEY | MDBX_INTEGERDUP)) { + assert(params.maxlen == params.minlen); + assert(params.minlen == 4 || params.minlen == 8); + if (is_byteorder_le() || params.minlen == 8) + out.u64 = serial; + else + out.u32 = (uint32_t)serial; + } else if (params.flags & (MDBX_REVERSEKEY | MDBX_REVERSEDUP)) { + if (out.value.iov_len > 8) { + memset(out.bytes, '\0', out.value.iov_len - 8); + unaligned::store(out.bytes + out.value.iov_len - 8, htobe64(serial)); + } else { + out.u64 = htobe64(serial); + if (out.value.iov_len < 8) { + out.value.iov_len = std::max(length(serial), out.value.iov_len); + out.value.iov_base = out.bytes + 8 - out.value.iov_len; + } + } + } else { + out.u64 = htole64(serial); + if (out.value.iov_len > 8) + memset(out.bytes + 8, '\0', out.value.iov_len - 8); + else + out.value.iov_len = std::max(length(serial), out.value.iov_len); + } + + assert(out.value.iov_len >= params.minlen); + assert(out.value.iov_len <= params.maxlen); + assert(out.value.iov_len >= length(serial)); + assert(out.value.iov_base >= out.bytes); + assert((uint8_t *)out.value.iov_base + out.value.iov_len <= + out.bytes + out.limit); +} + +} /* namespace keygen */ diff --git a/plugins/Dbx_mdbx/src/libmdbx/test/keygen.h b/plugins/Dbx_mdbx/src/libmdbx/test/keygen.h new file mode 100644 index 0000000000..911ea6d89b --- /dev/null +++ b/plugins/Dbx_mdbx/src/libmdbx/test/keygen.h @@ -0,0 +1,125 @@ +/* + * Copyright 2017 Leonid Yuriev <leo@yuriev.ru> + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#pragma once + +#include "base.h" +#include "config.h" +#include "log.h" +#include "utils.h" + +namespace keygen { + +/* Под "генерацией ключей" здесь понимается генерация обоих значений для + * пар key-value, т.е. не только ключей, но и ассоциированных с ними данных. + */ + +/* Генерацию ключей нельзя отнести к простым задачам, так как требования + * примерно следующие: + * - генерация разного количества уникальных ключей различной длины + * в задаваемом диапазоне; + * - возможность выбора как псевдо-случайного порядка ключей, + * так и по некоторым специфическим законам (ограниченными упорядоченными + * последовательностями, в шахматном порядке по граница диапазона и т.д.); + * - возможность генерации дубликатов с задаваемым законом распределения; + * - возможность генерации непересекающимися кластерами для параллельного + * использования в нескольких потоках; + * - использовать минимум ресурсов, как CPU, так и RAM, в том числе + * включая cache pollution и ram bandwidth. + * + * При этом заведомо известно, что для MDBX не имеет значения: + * - используемый алфавит (значения байтов); + * - частотное распределение по алфавиту; + * - абсолютное значение ключей или разность между отдельными значениями; + * + * Соответственно, в общих чертах, схема генерации следующая: + * - вводится плоская одномерная "координата" uint64_t; + * - генерация специфических паттернов (последовательностей) + * реализуется посредством соответствующих преобразований "координат", при + * этом все подобные преобразования выполняются только над "координатой"; + * - итоговая "координата" преобразуется в 8-байтное суррогатное значение + * ключа; + * - для получения ключей длиной МЕНЕЕ 8 байт суррогат может усекаться + * до ненулевых байт, в том числе до нулевой длины; + * - для получения ключей длиной БОЛЕЕ 8 байт суррогат дополняется + * нулями или псевдослучайной последовательностью; + * + * Механизм генерации паттернов: + * - реализованный механизм является компромиссом между скоростью/простотой + * и гибкостью, необходимой для получения последовательностей, которых + * будет достаточно для проверки сценариев разделения и слияния страниц + * с данными внутри mdbx; + * - псевдо-случайные паттерны реализуются посредством набора инъективных + * отображающих функций; + * - не-псевдо-случайные паттерны реализуются посредством параметризируемого + * трех-этапного преобразования: + * 1) смещение (сложение) по модулю; + * 2) циклический сдвиг; + * 3) добавление абсолютного смещения (базы); + */ + +typedef uint64_t serial_t; + +enum : serial_t { + serial_minwith = 8, + serial_maxwith = sizeof(serial_t) * 8, + serial_allones = ~(serial_t)0 +}; + +struct result { + MDBX_val value; + size_t limit; + union { + uint8_t bytes[sizeof(uint64_t)]; + uint32_t u32; + uint64_t u64; + }; +}; + +//----------------------------------------------------------------------------- + +struct buffer_deleter : public std::unary_function<void, result *> { + void operator()(result *buffer) const { free(buffer); } +}; + +typedef std::unique_ptr<result, buffer_deleter> buffer; + +buffer alloc(size_t limit); + +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 ¶ms, 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 */ diff --git a/plugins/Dbx_mdbx/src/libmdbx/test/log.cc b/plugins/Dbx_mdbx/src/libmdbx/test/log.cc new file mode 100644 index 0000000000..5ab9951338 --- /dev/null +++ b/plugins/Dbx_mdbx/src/libmdbx/test/log.cc @@ -0,0 +1,287 @@ +/* + * Copyright 2017 Leonid Yuriev <leo@yuriev.ru> + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "test.h" + +static void fflushall() { fflush(nullptr); } + +void failure(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + fflushall(); + logging::output(logging::failure, fmt, ap); + va_end(ap); + fflushall(); + exit(EXIT_FAILURE); +} + +const char *test_strerror(int errnum) { + static __thread char buf[1024]; + return mdbx_strerror_r(errnum, buf, sizeof(buf)); +} + +void __noreturn failure_perror(const char *what, int errnum) { + failure("%s failed: %s (%d)\n", what, test_strerror(errnum), errnum); +} + +//----------------------------------------------------------------------------- + +namespace logging { + +static std::string prefix; +static std::string suffix; +static loglevel level; +static FILE *last; + +void setup(loglevel _level, const std::string &_prefix) { + level = (_level > error) ? failure : _level; + prefix = _prefix; +} + +void setup(const std::string &_prefix) { prefix = _prefix; } + +const char *level2str(const loglevel alevel) { + switch (alevel) { + default: + return "invalid/unknown"; + case extra: + return "extra"; + case trace: + return "trace"; + case verbose: + return "verbose"; + case info: + return "info"; + case notice: + return "notice"; + case warning: + return "warning"; + case error: + return "error"; + case failure: + return "failure"; + } +} + +bool output(const loglevel priority, const char *format, ...) { + if (priority < level) + return false; + + va_list ap; + va_start(ap, format); + output(priority, format, ap); + va_end(ap); + return true; +} + +bool output(const logging::loglevel priority, const char *format, va_list ap) { + if (last) { + putc('\n', last); + fflush(last); + last = nullptr; + } + + if (priority < level) + return false; + + chrono::time now = chrono::now_realtime(); + struct tm tm; +#ifdef _MSC_VER + int rc = _localtime32_s(&tm, (const __time32_t *)&now.utc); +#else + time_t time = now.utc; + int rc = localtime_r(&time, &tm) ? MDBX_SUCCESS : errno; +#endif + if (rc != MDBX_SUCCESS) + failure_perror("localtime_r()", rc); + + last = stdout; + fprintf(last, + "[ %02d%02d%02d-%02d:%02d:%02d.%06d_%05u %-10s %.4s ] %s" /* TODO */, + tm.tm_year - 100, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, + tm.tm_sec, chrono::fractional2us(now.fractional), osal_getpid(), + prefix.c_str(), level2str(priority), suffix.c_str()); + + va_list ones; + memset(&ones, 0, sizeof(ones)) /* zap MSVC and other stupid compilers */; + if (priority >= error) + va_copy(ones, ap); + vfprintf(last, format, ap); + + size_t len = strlen(format); + char end = len ? format[len - 1] : '\0'; + + switch (end) { + default: + putc('\n', last); + // fall through + case '\n': + fflush(last); + last = nullptr; + // fall through + case ' ': + case '_': + case ':': + case '|': + case ',': + case '\t': + case '\b': + case '\r': + case '\0': + break; + } + + if (priority >= error) { + if (last != stderr) { + fprintf(stderr, "[ %05u %-10s %.4s ] %s", osal_getpid(), prefix.c_str(), + level2str(priority), suffix.c_str()); + vfprintf(stderr, format, ones); + if (end != '\n') + putc('\n', stderr); + fflush(stderr); + } + va_end(ones); + } + + return true; +} + +bool feed(const char *format, va_list ap) { + if (!last) + return false; + + vfprintf(last, format, ap); + size_t len = strlen(format); + if (len && format[len - 1] == '\n') { + fflush(last); + last = nullptr; + } + return true; +} + +bool feed(const char *format, ...) { + if (!last) + return false; + + va_list ap; + va_start(ap, format); + feed(format, ap); + va_end(ap); + return true; +} + +local_suffix::local_suffix(const char *c_str) + : trim_pos(suffix.size()), indent(0) { + suffix.append(c_str); +} + +local_suffix::local_suffix(const std::string &str) + : trim_pos(suffix.size()), indent(0) { + suffix.append(str); +} + +void local_suffix::push() { + indent += 1; + suffix.push_back('\t'); +} + +void local_suffix::pop() { + assert(indent > 0); + if (indent > 0) { + indent -= 1; + suffix.pop_back(); + } +} + +local_suffix::~local_suffix() { suffix.erase(trim_pos); } + +} /* namespace log */ + +void log_extra(const char *msg, ...) { + if (logging::extra >= logging::level) { + va_list ap; + va_start(ap, msg); + logging::output(logging::extra, msg, ap); + va_end(ap); + } else + logging::last = nullptr; +} + +void log_trace(const char *msg, ...) { + if (logging::trace >= logging::level) { + va_list ap; + va_start(ap, msg); + logging::output(logging::trace, msg, ap); + va_end(ap); + } else + logging::last = nullptr; +} + +void log_verbose(const char *msg, ...) { + if (logging::verbose >= logging::level) { + va_list ap; + va_start(ap, msg); + logging::output(logging::verbose, msg, ap); + va_end(ap); + } else + logging::last = nullptr; +} + +void log_info(const char *msg, ...) { + if (logging::info >= logging::level) { + va_list ap; + va_start(ap, msg); + logging::output(logging::info, msg, ap); + va_end(ap); + } else + logging::last = nullptr; +} + +void log_notice(const char *msg, ...) { + if (logging::notice >= logging::level) { + va_list ap; + va_start(ap, msg); + logging::output(logging::notice, msg, ap); + va_end(ap); + } else + logging::last = nullptr; +} + +void log_warning(const char *msg, ...) { + if (logging::warning >= logging::level) { + va_list ap; + va_start(ap, msg); + logging::output(logging::warning, msg, ap); + va_end(ap); + } else + logging::last = nullptr; +} + +void log_error(const char *msg, ...) { + if (logging::error >= logging::level) { + va_list ap; + va_start(ap, msg); + logging::output(logging::error, msg, ap); + va_end(ap); + } else + logging::last = nullptr; +} + +void log_trouble(const char *where, const char *what, int errnum) { + log_error("%s: %s %s", where, what, test_strerror(errnum)); +} + +bool log_enabled(const logging::loglevel priority) { + return (priority >= logging::level); +} diff --git a/plugins/Dbx_mdbx/src/libmdbx/test/log.h b/plugins/Dbx_mdbx/src/libmdbx/test/log.h new file mode 100644 index 0000000000..3ffc21d2e8 --- /dev/null +++ b/plugins/Dbx_mdbx/src/libmdbx/test/log.h @@ -0,0 +1,90 @@ +/* + * Copyright 2017 Leonid Yuriev <leo@yuriev.ru> + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#pragma once + +#include "base.h" + +void __noreturn usage(void); + +#ifdef __GNUC__ +#define __printf_args(format_index, first_arg) \ + __attribute__((format(printf, format_index, first_arg))) +#else +#define __printf_args(format_index, first_arg) +#endif + +void __noreturn __printf_args(1, 2) failure(const char *fmt, ...); + +void __noreturn failure_perror(const char *what, int errnum); +const char *test_strerror(int errnum); + +namespace logging { + +enum loglevel { + extra, + trace, + verbose, + info, + notice, + warning, + error, + failure, +}; + +const char *level2str(const loglevel level); +void setup(loglevel level, const std::string &prefix); +void setup(const std::string &prefix); + +bool output(const loglevel priority, const char *format, va_list ap); +bool __printf_args(2, 3) + output(const loglevel priority, const char *format, ...); +bool feed(const char *format, va_list ap); +bool __printf_args(1, 2) feed(const char *format, ...); + +class local_suffix { +protected: + size_t trim_pos; + int indent; + +public: + local_suffix(const local_suffix &) = delete; + local_suffix(const local_suffix &&) = delete; + const local_suffix &operator=(const local_suffix &) = delete; + + local_suffix(const char *c_str); + local_suffix(const std::string &str); + void push(); + void pop(); + ~local_suffix(); +}; + +} /* namespace log */ + +void __printf_args(1, 2) log_extra(const char *msg, ...); +void __printf_args(1, 2) log_trace(const char *msg, ...); +void __printf_args(1, 2) log_verbose(const char *msg, ...); +void __printf_args(1, 2) log_info(const char *msg, ...); +void __printf_args(1, 2) log_notice(const char *msg, ...); +void __printf_args(1, 2) log_warning(const char *msg, ...); +void __printf_args(1, 2) log_error(const char *msg, ...); + +void log_trouble(const char *where, const char *what, int errnum); +bool log_enabled(const logging::loglevel priority); + +#ifdef _DEBUG +#define TRACE(...) log_trace(__VA_ARGS__) +#else +#define TRACE(...) __noop(__VA_ARGS__) +#endif diff --git a/plugins/Dbx_mdbx/src/libmdbx/test/main.cc b/plugins/Dbx_mdbx/src/libmdbx/test/main.cc new file mode 100644 index 0000000000..2c4f9b09fd --- /dev/null +++ b/plugins/Dbx_mdbx/src/libmdbx/test/main.cc @@ -0,0 +1,397 @@ +/* + * Copyright 2017 Leonid Yuriev <leo@yuriev.ru> + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "test.h" + +void __noreturn usage(void) { + printf("usage:\n" + "\tFIXME\n"); + exit(EXIT_FAILURE); +} + +//----------------------------------------------------------------------------- + +void actor_params::set_defaults(void) { + pathname_log = ""; + loglevel = +#ifdef NDEBUG + logging::notice; +#else + logging::trace; +#endif + + pathname_db = +#ifdef __linux__ + "/dev/shm/test_tmpdb.mdbx"; +#else + "test_tmpdb.mdbx"; +#endif + mode_flags = MDBX_NOSUBDIR | MDBX_WRITEMAP | MDBX_MAPASYNC | MDBX_NORDAHEAD | + MDBX_NOMEMINIT | MDBX_COALESCE | MDBX_LIFORECLAIM; + table_flags = MDBX_DUPSORT; + size = 1024 * 1024 * 4; + + 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; + nrepeat = 1; + nthreads = 1; + + keylen_min = 0; + keylen_max = 42; + datalen_min = 0; + datalen_max = 256; + + batch_read = 4; + batch_write = 4; + + delaystart = 0; + waitfor_nops = 0; + + drop_table = false; + + max_readers = 42; + max_tables = 42; + + global::config::timeout_duration_seconds = 0 /* infinite */; + global::config::dump_config = true; + global::config::cleanup_before = true; + global::config::cleanup_after = true; + global::config::failfast = true; + global::config::progress_indicator = osal_istty(STDERR_FILENO); +} + +namespace global { + +std::vector<actor_config> actors; +std::unordered_map<unsigned, actor_config *> events; +std::unordered_map<mdbx_pid_t, actor_config *> pid2actor; +std::set<std::string> databases; +unsigned nactors; +chrono::time start_motonic; +chrono::time deadline_motonic; +bool singlemode; + +namespace config { +unsigned timeout_duration_seconds; +bool dump_config; +bool cleanup_before; +bool cleanup_after; +bool failfast; +bool progress_indicator; +} /* namespace config */ + +} /* namespace global */ + +//----------------------------------------------------------------------------- + +const char global::thunk_param_prefix[] = "--execute="; + +std::string thunk_param(const actor_config &config) { + return config.serialize(global::thunk_param_prefix); +} + +void cleanup() { + log_trace(">> cleanup"); + /* TODO: remove each database */ + log_trace("<< cleanup"); +} + +int main(int argc, char *const argv[]) { + +#ifdef _DEBUG + log_trace("#argc = %d", argc); + for (int i = 0; i < argc; ++i) + log_trace("#argv[%d] = %s", i, argv[i]); +#endif /* _DEBUG */ + + if (argc < 2) + failure("No parameters given\n"); + + if (argc == 2 && + strncmp(argv[1], global::thunk_param_prefix, + strlen(global::thunk_param_prefix)) == 0) + return test_execute( + actor_config(argv[1] + strlen(global::thunk_param_prefix))) + ? EXIT_SUCCESS + : EXIT_FAILURE; + + actor_params params; + params.set_defaults(); + global::config::dump_config = true; + logging::setup((logging::loglevel)params.loglevel, "main"); + 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, last_space_id); + continue; + } + if (config::parse_option(argc, argv, narg, "pathname", params.pathname_db)) + continue; + if (config::parse_option(argc, argv, narg, "mode", params.mode_flags, + config::mode_bits)) + continue; + if (config::parse_option(argc, argv, narg, "table", params.table_flags, + config::table_bits)) + continue; + if (config::parse_option(argc, argv, narg, "size", params.size, + config::binary, 4096 * 4)) + continue; + + 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; + if (config::parse_option(argc, argv, narg, "threads", params.nthreads, + config::no_scale, 1, 64)) + continue; + if (config::parse_option(argc, argv, narg, "timeout", + global::config::timeout_duration_seconds, + config::duration, 1)) + continue; + if (config::parse_option(argc, argv, narg, "keylen.min", params.keylen_min, + config::no_scale, 0, params.keylen_max)) + continue; + if (config::parse_option(argc, argv, narg, "keylen.max", params.keylen_max, + config::no_scale, params.keylen_min, + mdbx_get_maxkeysize(0))) + continue; + if (config::parse_option(argc, argv, narg, "datalen.min", + params.datalen_min, config::no_scale, 0, + params.datalen_max)) + continue; + if (config::parse_option(argc, argv, narg, "datalen.max", + params.datalen_max, config::no_scale, + params.datalen_min, MDBX_MAXDATASIZE)) + continue; + if (config::parse_option(argc, argv, narg, "batch.read", params.batch_read, + config::no_scale, 1)) + continue; + if (config::parse_option(argc, argv, narg, "batch.write", + params.batch_write, config::no_scale, 1)) + continue; + if (config::parse_option(argc, argv, narg, "delay", params.delaystart, + config::duration)) + continue; + if (config::parse_option(argc, argv, narg, "wait4ops", params.waitfor_nops, + config::decimal)) + continue; + if (config::parse_option(argc, argv, narg, "drop", params.drop_table)) + continue; + if (config::parse_option(argc, argv, narg, "dump-config", + global::config::dump_config)) + continue; + if (config::parse_option(argc, argv, narg, "cleanup-before", + global::config::cleanup_before)) + continue; + if (config::parse_option(argc, argv, narg, "cleanup-after", + global::config::cleanup_after)) + continue; + if (config::parse_option(argc, argv, narg, "max-readers", + params.max_readers, config::no_scale, 1, 255)) + continue; + if (config::parse_option(argc, argv, narg, "max-tables", params.max_tables, + config::no_scale, 1, INT16_MAX)) + continue; + + if (config::parse_option(argc, argv, narg, "no-delay", nullptr)) { + params.delaystart = 0; + continue; + } + if (config::parse_option(argc, argv, narg, "no-wait", nullptr)) { + params.waitfor_nops = 0; + continue; + } + if (config::parse_option(argc, argv, narg, "duration", params.test_duration, + config::duration, 1)) { + params.test_nops = 0; + continue; + } + if (config::parse_option(argc, argv, narg, "nops", params.test_nops, + config::decimal, 1)) { + params.test_duration = 0; + continue; + } + 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(last_space_id, ac_jitter, value, params); + continue; + } + if (config::parse_option(argc, argv, narg, "dead.reader", nullptr)) { + configure_actor(last_space_id, ac_deadread, value, params); + continue; + } + if (config::parse_option(argc, argv, narg, "dead.writer", nullptr)) { + configure_actor(last_space_id, ac_deadwrite, value, params); + continue; + } + if (config::parse_option(argc, argv, narg, "failfast", + global::config::failfast)) + continue; + if (config::parse_option(argc, argv, narg, "progress", + global::config::progress_indicator)) + continue; + + if (*argv[narg] != '-') + testcase_setup(argv[narg], params, last_space_id); + else + failure("Unknown option '%s'\n", argv[narg]); + } + + if (global::config::dump_config) + config::dump(); + + //-------------------------------------------------------------------------- + + if (global::actors.empty()) { + log_notice("no testcase(s) configured, exiting"); + return EXIT_SUCCESS; + } + + bool failed = false; + global::start_motonic = chrono::now_motonic(); + global::deadline_motonic.fixedpoint = + (global::config::timeout_duration_seconds == 0) + ? chrono::infinite().fixedpoint + : global::start_motonic.fixedpoint + + chrono::from_seconds(global::config::timeout_duration_seconds) + .fixedpoint; + + if (global::config::cleanup_before) + cleanup(); + + if (global::actors.size() == 1) { + logging::setup("main"); + global::singlemode = true; + if (!test_execute(global::actors.front())) + failed = true; + } else { + logging::setup("overlord"); + + log_trace("=== preparing..."); + log_trace(">> osal_setup"); + osal_setup(global::actors); + log_trace("<< osal_setup"); + + for (auto &a : global::actors) { + mdbx_pid_t pid; + log_trace(">> actor_start"); + int rc = osal_actor_start(a, pid); + log_trace("<< actor_start"); + if (rc) { + log_trace(">> killall_actors: (%s)", "start failed"); + osal_killall_actors(); + log_trace("<< killall_actors"); + failure("Failed to start actor #%u (%s)\n", a.actor_id, + test_strerror(rc)); + } + global::pid2actor[pid] = &a; + } + + log_trace("=== ready to start..."); + atexit(osal_killall_actors); + log_trace(">> wait4barrier"); + osal_wait4barrier(); + log_trace("<< wait4barrier"); + + size_t left = global::actors.size(); + log_trace("=== polling..."); + while (left > 0) { + unsigned timeout_seconds_left = INT_MAX; + chrono::time now_motonic = chrono::now_motonic(); + if (now_motonic.fixedpoint >= global::deadline_motonic.fixedpoint) + timeout_seconds_left = 0; + else { + chrono::time left_motonic; + left_motonic.fixedpoint = + global::deadline_motonic.fixedpoint - now_motonic.fixedpoint; + timeout_seconds_left = left_motonic.seconds(); + } + + mdbx_pid_t pid; + int rc = osal_actor_poll(pid, timeout_seconds_left); + if (rc) + failure("Poll error: %s (%d)\n", test_strerror(rc), rc); + + if (pid) { + actor_status status = osal_actor_info(pid); + actor_config *actor = global::pid2actor.at(pid); + if (!actor) + continue; + + log_info("actor #%u, id %d, pid %u: %s\n", actor->actor_id, + actor->space_id, pid, status2str(status)); + if (status > as_running) { + left -= 1; + if (status != as_successful) { + if (global::config::failfast && !failed) { + log_trace(">> killall_actors: (%s)", "failfast"); + osal_killall_actors(); + log_trace("<< killall_actors"); + } + failed = true; + } + } + } else { + if (timeout_seconds_left == 0) + failure("Timeout\n"); + } + } + log_trace("=== done..."); + } + + log_notice("RESULT: %s\n", failed ? "Failed" : "Successful"); + if (global::config::cleanup_before) { + if (failed) + log_info("skip cleanup"); + else + cleanup(); + } + return failed ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/plugins/Dbx_mdbx/src/libmdbx/test/osal-unix.cc b/plugins/Dbx_mdbx/src/libmdbx/test/osal-unix.cc new file mode 100644 index 0000000000..88a10f1175 --- /dev/null +++ b/plugins/Dbx_mdbx/src/libmdbx/test/osal-unix.cc @@ -0,0 +1,274 @@ +/* + * Copyright 2017 Leonid Yuriev <leo@yuriev.ru> + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "test.h" + +#include <pthread.h> +#include <signal.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +struct shared_t { + pthread_barrier_t barrier; + pthread_mutex_t mutex; + size_t conds_size; + pthread_cond_t conds[1]; +}; + +static shared_t *shared; + +void osal_wait4barrier(void) { + assert(shared != nullptr && shared != MAP_FAILED); + int rc = pthread_barrier_wait(&shared->barrier); + if (rc != 0 && rc != PTHREAD_BARRIER_SERIAL_THREAD) { + failure_perror("pthread_barrier_wait(shared)", rc); + } +} + +void osal_setup(const std::vector<actor_config> &actors) { + assert(shared == nullptr); + + pthread_mutexattr_t mutexattr; + int rc = pthread_mutexattr_init(&mutexattr); + if (rc) + failure_perror("pthread_mutexattr_init()", rc); + rc = pthread_mutexattr_setpshared(&mutexattr, PTHREAD_PROCESS_SHARED); + if (rc) + failure_perror("pthread_mutexattr_setpshared()", rc); + + pthread_barrierattr_t barrierattr; + rc = pthread_barrierattr_init(&barrierattr); + if (rc) + failure_perror("pthread_barrierattr_init()", rc); + rc = pthread_barrierattr_setpshared(&barrierattr, PTHREAD_PROCESS_SHARED); + if (rc) + failure_perror("pthread_barrierattr_setpshared()", rc); + + pthread_condattr_t condattr; + rc = pthread_condattr_init(&condattr); + if (rc) + failure_perror("pthread_condattr_init()", rc); + rc = pthread_condattr_setpshared(&condattr, PTHREAD_PROCESS_SHARED); + if (rc) + failure_perror("pthread_condattr_setpshared()", rc); + + shared = (shared_t *)mmap( + nullptr, sizeof(shared_t) + actors.size() * sizeof(pthread_cond_t), + PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); + if (MAP_FAILED == (void *)shared) + failure_perror("mmap(shared_conds)", errno); + + rc = pthread_mutex_init(&shared->mutex, &mutexattr); + if (rc) + failure_perror("pthread_mutex_init(shared)", rc); + + rc = pthread_barrier_init(&shared->barrier, &barrierattr, actors.size() + 1); + if (rc) + failure_perror("pthread_barrier_init(shared)", rc); + + const size_t n = actors.size() + 1; + for (size_t i = 0; i < n; ++i) { + pthread_cond_t *event = &shared->conds[i]; + rc = pthread_cond_init(event, &condattr); + if (rc) + failure_perror("pthread_cond_init(shared)", rc); + log_trace("osal_setup: event(shared pthread_cond) %" PRIuPTR " -> %p", i, + event); + } + shared->conds_size = actors.size() + 1; + + pthread_barrierattr_destroy(&barrierattr); + pthread_condattr_destroy(&condattr); + pthread_mutexattr_destroy(&mutexattr); +} + +void osal_broadcast(unsigned id) { + assert(shared != nullptr && shared != MAP_FAILED); + log_trace("osal_broadcast: event %u", id); + if (id >= shared->conds_size) + failure("osal_broadcast: id > limit"); + int rc = pthread_cond_broadcast(shared->conds + id); + if (rc) + failure_perror("sem_post(shared)", rc); +} + +int osal_waitfor(unsigned id) { + assert(shared != nullptr && shared != MAP_FAILED); + + log_trace("osal_waitfor: event %u", id); + if (id >= shared->conds_size) + failure("osal_waitfor: id > limit"); + + int rc = pthread_mutex_lock(&shared->mutex); + if (rc != 0) + failure_perror("pthread_mutex_lock(shared)", rc); + + rc = pthread_cond_wait(shared->conds + id, &shared->mutex); + if (rc && rc != EINTR) + failure_perror("pthread_cond_wait(shared)", rc); + + rc = pthread_mutex_unlock(&shared->mutex); + if (rc != 0) + failure_perror("pthread_mutex_unlock(shared)", rc); + + return (rc == 0) ? true : false; +} + +//----------------------------------------------------------------------------- + +const std::string +actor_config::osal_serialize(simple_checksum &checksum) const { + (void)checksum; + /* not used in workload, but just for testing */ + return "unix.fork"; +} + +bool actor_config::osal_deserialize(const char *str, const char *end, + simple_checksum &checksum) { + (void)checksum; + /* not used in workload, but just for testing */ + return strncmp(str, "unix.fork", 9) == 0 && str + 9 == end; +} + +//----------------------------------------------------------------------------- + +static std::unordered_map<pid_t, actor_status> childs; + +static void handler_SIGCHLD(int unused) { (void)unused; } + +mdbx_pid_t osal_getpid(void) { return getpid(); } + +int osal_delay(unsigned seconds) { return sleep(seconds) ? errno : 0; } + +int osal_actor_start(const actor_config &config, mdbx_pid_t &pid) { + if (childs.empty()) + signal(SIGCHLD, handler_SIGCHLD); + + pid = fork(); + + if (pid == 0) { + const bool result = test_execute(config); + exit(result ? EXIT_SUCCESS : EXIT_FAILURE); + } + + if (pid < 0) + return errno; + + log_trace("osal_actor_start: fork pid %i for %u", pid, config.actor_id); + childs[pid] = as_running; + return 0; +} + +actor_status osal_actor_info(const mdbx_pid_t pid) { return childs.at(pid); } + +void osal_killall_actors(void) { + for (auto &pair : childs) { + kill(pair.first, SIGKILL); + pair.second = as_killed; + } +} + +int osal_actor_poll(mdbx_pid_t &pid, unsigned timeout) { +retry: + int status, options = WNOHANG; +#ifdef WUNTRACED + options |= WUNTRACED; +#endif +#ifdef WCONTINUED + options |= WCONTINUED; +#endif + pid = waitpid(0, &status, options); + + if (pid > 0) { + if (WIFEXITED(status)) + childs[pid] = + (WEXITSTATUS(status) == EXIT_SUCCESS) ? as_successful : as_failed; + else if (WIFSIGNALED(status) || WCOREDUMP(status)) + childs[pid] = as_killed; + else if (WIFSTOPPED(status)) + childs[pid] = as_debuging; + else if (WIFCONTINUED(status)) + childs[pid] = as_running; + else { + assert(false); + } + return 0; + } + + if (pid == 0) { + if (timeout && sleep(timeout)) + goto retry; + return 0; + } + + switch (errno) { + case EINTR: + pid = 0; + return 0; + + case ECHILD: + default: + pid = 0; + return errno; + } +} + +void osal_yield(void) { + if (sched_yield()) + failure_perror("sched_yield()", errno); +} + +void osal_udelay(unsigned us) { + chrono::time until, now = chrono::now_motonic(); + until.fixedpoint = now.fixedpoint + chrono::from_us(us).fixedpoint; + struct timespec ts; + + static unsigned threshold_us; + if (threshold_us == 0) { + if (clock_getres(CLOCK_PROCESS_CPUTIME_ID, &ts)) { + int rc = errno; + failure_perror("clock_getres(CLOCK_PROCESS_CPUTIME_ID)", rc); + } + chrono::time threshold = chrono::from_timespec(ts); + assert(threshold.seconds() == 0); + + threshold_us = chrono::fractional2us(threshold.fractional); + if (threshold_us < 1000) + threshold_us = 1000; + } + + ts.tv_sec = ts.tv_nsec = 0; + if (us > threshold_us) { + ts.tv_sec = us / 1000000u; + ts.tv_nsec = (us % 1000000u) * 1000u; + } + + do { + if (us > threshold_us) { + if (nanosleep(&ts, &ts)) { + int rc = errno; + /* if (rc == EINTR) { ... } ? */ + failure_perror("usleep()", rc); + } + us = ts.tv_sec * 1000000u + ts.tv_nsec / 1000u; + } + cpu_relax(); + + now = chrono::now_motonic(); + } while (until.fixedpoint > now.fixedpoint); +} + +bool osal_istty(int fd) { return isatty(fd) == 1; } diff --git a/plugins/Dbx_mdbx/src/libmdbx/test/osal-windows.cc b/plugins/Dbx_mdbx/src/libmdbx/test/osal-windows.cc new file mode 100644 index 0000000000..57f7f547f2 --- /dev/null +++ b/plugins/Dbx_mdbx/src/libmdbx/test/osal-windows.cc @@ -0,0 +1,307 @@ +/* + * Copyright 2017 Leonid Yuriev <leo@yuriev.ru> + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "test.h" + +static std::unordered_map<unsigned, HANDLE> events; +static HANDLE hBarrierSemaphore, hBarrierEvent; + +static int waitstatus2errcode(DWORD result) { + switch (result) { + case WAIT_OBJECT_0: + return MDBX_SUCCESS; + case WAIT_FAILED: + return GetLastError(); + case WAIT_ABANDONED: + return ERROR_ABANDONED_WAIT_0; + case WAIT_IO_COMPLETION: + return ERROR_USER_APC; + case WAIT_TIMEOUT: + return ERROR_TIMEOUT; + default: + return ERROR_UNHANDLED_ERROR; + } +} + +void osal_wait4barrier(void) { + DWORD rc = WaitForSingleObject(hBarrierSemaphore, 0); + switch (rc) { + default: + failure_perror("WaitForSingleObject(BarrierSemaphore)", + waitstatus2errcode(rc)); + case WAIT_OBJECT_0: + rc = WaitForSingleObject(hBarrierEvent, INFINITE); + if (rc != WAIT_OBJECT_0) + failure_perror("WaitForSingleObject(BarrierEvent)", + waitstatus2errcode(rc)); + break; + case WAIT_TIMEOUT: + if (!SetEvent(hBarrierEvent)) + failure_perror("SetEvent(BarrierEvent)", GetLastError()); + break; + } +} + +static HANDLE make_inharitable(HANDLE hHandle) { + assert(hHandle != NULL && hHandle != INVALID_HANDLE_VALUE); + if (!DuplicateHandle(GetCurrentProcess(), hHandle, GetCurrentProcess(), + &hHandle, 0, TRUE, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) + failure_perror("DuplicateHandle()", GetLastError()); + return hHandle; +} + +void osal_setup(const std::vector<actor_config> &actors) { + assert(events.empty()); + const size_t n = actors.size() + 1; + events.reserve(n); + + for (unsigned i = 0; i < n; ++i) { + HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!hEvent) + failure_perror("CreateEvent()", GetLastError()); + hEvent = make_inharitable(hEvent); + log_trace("osal_setup: event %" PRIuPTR " -> %p", i, hEvent); + events[i] = hEvent; + } + + hBarrierSemaphore = CreateSemaphore(NULL, 0, (LONG)actors.size(), NULL); + if (!hBarrierSemaphore) + failure_perror("CreateSemaphore(BarrierSemaphore)", GetLastError()); + hBarrierSemaphore = make_inharitable(hBarrierSemaphore); + + hBarrierEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!hBarrierEvent) + failure_perror("CreateEvent(BarrierEvent)", GetLastError()); + hBarrierEvent = make_inharitable(hBarrierEvent); +} + +void osal_broadcast(unsigned id) { + log_trace("osal_broadcast: event %u", id); + if (!SetEvent(events.at(id))) + failure_perror("SetEvent()", GetLastError()); +} + +int osal_waitfor(unsigned id) { + log_trace("osal_waitfor: event %u", id); + DWORD rc = WaitForSingleObject(events.at(id), INFINITE); + return waitstatus2errcode(rc); +} + +mdbx_pid_t osal_getpid(void) { return GetCurrentProcessId(); } + +int osal_delay(unsigned seconds) { + Sleep(seconds * 1000u); + return 0; +} + +//----------------------------------------------------------------------------- + +const std::string +actor_config::osal_serialize(simple_checksum &checksum) const { + checksum.push(hBarrierSemaphore); + checksum.push(hBarrierEvent); + + HANDLE hWait = INVALID_HANDLE_VALUE; + if (wait4id) { + hWait = events.at(wait4id); + checksum.push(hWait); + } + + HANDLE hSignal = INVALID_HANDLE_VALUE; + if (wanna_event4signalling()) { + hSignal = events.at(actor_id); + checksum.push(hSignal); + } + + return format("%p.%p.%p.%p", hBarrierSemaphore, hBarrierEvent, hWait, + hSignal); +} + +bool actor_config::osal_deserialize(const char *str, const char *end, + simple_checksum &checksum) { + + std::string copy(str, end - str); + TRACE(">> osal_deserialize(%s)\n", copy.c_str()); + + assert(hBarrierSemaphore == 0); + assert(hBarrierEvent == 0); + assert(events.empty()); + + HANDLE hWait, hSignal; + if (sscanf_s(copy.c_str(), "%p.%p.%p.%p", &hBarrierSemaphore, &hBarrierEvent, + &hWait, &hSignal) != 4) { + TRACE("<< osal_deserialize: failed\n"); + return false; + } + + checksum.push(hBarrierSemaphore); + checksum.push(hBarrierEvent); + + if (wait4id) { + checksum.push(hWait); + events[wait4id] = hWait; + } + + if (wanna_event4signalling()) { + checksum.push(hSignal); + events[actor_id] = hSignal; + } + + TRACE("<< osal_deserialize: OK\n"); + return true; +} + +//----------------------------------------------------------------------------- + +typedef std::pair<HANDLE, actor_status> child; +static std::unordered_map<mdbx_pid_t, child> childs; + +int osal_actor_start(const actor_config &config, mdbx_pid_t &pid) { + if (childs.size() == MAXIMUM_WAIT_OBJECTS) + failure("Could't manage more that %u actors on Windows\n", + MAXIMUM_WAIT_OBJECTS); + + _flushall(); + + STARTUPINFOA StartupInfo; + GetStartupInfoA(&StartupInfo); + + char exename[_MAX_PATH]; + DWORD exename_size = sizeof(exename); + if (!QueryFullProcessImageNameA(GetCurrentProcess(), 0, exename, + &exename_size)) + failure_perror("QueryFullProcessImageName()", GetLastError()); + + std::string cmdline = "test_mdbx.child " + thunk_param(config); + + PROCESS_INFORMATION ProcessInformation; + if (!CreateProcessA(exename, const_cast<char *>(cmdline.c_str()), + NULL, // Retuned process handle is not inheritable. + NULL, // Retuned thread handle is not inheritable. + TRUE, // Child inherits all inheritable handles. + NORMAL_PRIORITY_CLASS | INHERIT_PARENT_AFFINITY, + NULL, // Inherit the parent's environment. + NULL, // Inherit the parent's current directory. + &StartupInfo, &ProcessInformation)) + return GetLastError(); + + CloseHandle(ProcessInformation.hThread); + pid = ProcessInformation.dwProcessId; + childs[pid] = std::make_pair(ProcessInformation.hProcess, as_running); + return 0; +} + +actor_status osal_actor_info(const mdbx_pid_t pid) { + actor_status status = childs.at(pid).second; + if (status > as_running) + return status; + + DWORD ExitCode; + if (!GetExitCodeProcess(childs.at(pid).first, &ExitCode)) + failure_perror("GetExitCodeProcess()", GetLastError()); + + switch (ExitCode) { + case STILL_ACTIVE: + return as_running; + case EXIT_SUCCESS: + status = as_successful; + break; + // case EXCEPTION_BREAKPOINT: + case EXCEPTION_SINGLE_STEP: + status = as_debuging; + break; + case STATUS_CONTROL_C_EXIT: + case EXCEPTION_NONCONTINUABLE_EXCEPTION: + status = as_killed; + break; + default: + status = as_failed; + break; + } + + childs.at(pid).second = status; + return status; +} + +void osal_killall_actors(void) { + for (auto &pair : childs) + TerminateProcess(pair.second.first, STATUS_CONTROL_C_EXIT); +} + +int osal_actor_poll(mdbx_pid_t &pid, unsigned timeout) { + std::vector<HANDLE> handles; + handles.reserve(childs.size()); + for (const auto &pair : childs) + if (pair.second.second <= as_running) + handles.push_back(pair.second.first); + + DWORD rc = + MsgWaitForMultipleObjectsEx((DWORD)handles.size(), &handles[0], + (timeout > 60) ? 60 * 1000 : timeout * 1000, + QS_ALLINPUT | QS_ALLPOSTMESSAGE, 0); + + if (rc >= WAIT_OBJECT_0 && rc < WAIT_OBJECT_0 + handles.size()) { + pid = 0; + for (const auto &pair : childs) + if (pair.second.first == handles[rc - WAIT_OBJECT_0]) { + pid = pair.first; + break; + } + return 0; + } + + if (rc == WAIT_TIMEOUT) { + pid = 0; + return 0; + } + + return waitstatus2errcode(rc); +} + +void osal_yield(void) { SwitchToThread(); } + +void osal_udelay(unsigned us) { + chrono::time until, now = chrono::now_motonic(); + until.fixedpoint = now.fixedpoint + chrono::from_us(us).fixedpoint; + + static unsigned threshold_us; + if (threshold_us == 0) { +#if 1 + unsigned timeslice_ms = 1; + while (timeBeginPeriod(timeslice_ms) == TIMERR_NOCANDO) + ++timeslice_ms; + threshold_us = timeslice_ms * 1500u; +#else + ULONGLONG InterruptTimePrecise_100ns; + QueryInterruptTimePrecise(&InterruptTimePrecise_100ns); + threshold_us = InterruptTimePrecise_100ns / 5; +#endif + assert(threshold_us > 0); + } + + do { + if (us > threshold_us && us > 1000) { + DWORD rc = SleepEx(us / 1000, TRUE); + if (rc) + failure_perror("SleepEx()", waitstatus2errcode(rc)); + us = 0; + } + + YieldProcessor(); + now = chrono::now_motonic(); + } while (now.fixedpoint < until.fixedpoint); +} + +bool osal_istty(int fd) { return _isatty(fd) != 0; } diff --git a/plugins/Dbx_mdbx/src/libmdbx/test/osal.h b/plugins/Dbx_mdbx/src/libmdbx/test/osal.h new file mode 100644 index 0000000000..8f1626350c --- /dev/null +++ b/plugins/Dbx_mdbx/src/libmdbx/test/osal.h @@ -0,0 +1,45 @@ +/* + * Copyright 2017 Leonid Yuriev <leo@yuriev.ru> + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#pragma once + +#include "base.h" + +void osal_setup(const std::vector<actor_config> &actors); +void osal_broadcast(unsigned id); +int osal_waitfor(unsigned id); + +int osal_actor_start(const actor_config &config, mdbx_pid_t &pid); +actor_status osal_actor_info(const mdbx_pid_t pid); +void osal_killall_actors(void); +int osal_actor_poll(mdbx_pid_t &pid, unsigned timeout); +void osal_wait4barrier(void); + +mdbx_pid_t osal_getpid(void); +int osal_delay(unsigned seconds); +void osal_udelay(unsigned us); +void osal_yield(void); +bool osal_istty(int fd); + +#ifdef _MSC_VER +#ifndef STDIN_FILENO +#define STDIN_FILENO _fileno(stdin) +#endif +#ifndef STDOUT_FILENO +#define STDOUT_FILENO _fileno(stdout) +#endif +#ifndef STDERR_FILENO +#define STDERR_FILENO _fileno(stderr) +#endif +#endif /* _MSC_VER */ diff --git a/plugins/Dbx_mdbx/src/libmdbx/test/test.cc b/plugins/Dbx_mdbx/src/libmdbx/test/test.cc new file mode 100644 index 0000000000..f1dc226465 --- /dev/null +++ b/plugins/Dbx_mdbx/src/libmdbx/test/test.cc @@ -0,0 +1,473 @@ +/* + * Copyright 2017 Leonid Yuriev <leo@yuriev.ru> + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "test.h" + +const char *testcase2str(const actor_testcase testcase) { + switch (testcase) { + default: + assert(false); + return "?!"; + case ac_none: + return "none"; + case ac_hill: + return "hill"; + case ac_deadread: + return "deadread"; + case ac_deadwrite: + return "deadwrite"; + case ac_jitter: + return "jitter"; + case ac_try: + return "try"; + } +} + +const char *status2str(actor_status status) { + switch (status) { + default: + assert(false); + return "?!"; + case as_debuging: + return "debuging"; + case as_running: + return "running"; + case as_successful: + return "successful"; + case as_killed: + return "killed"; + case as_failed: + return "failed"; + } +} + +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_logger(int type, const char *function, int line, + const char *msg, va_list args) { + logging::loglevel level = logging::info; + if (type & MDBX_DBG_EXTRA) + level = logging::extra; + if (type & MDBX_DBG_TRACE) + level = logging::trace; + if (type & MDBX_DBG_PRINT) + level = logging::verbose; + + if (!function) + function = "unknown"; + if (type & MDBX_DBG_ASSERT) { + log_error("mdbx: assertion failure: %s, %d", function, line); + level = logging::failure; + } + + if (logging::output(level, strncmp(function, "mdbx_", 5) == 0 ? "%s: " + : "mdbx: %s: ", + function)) + logging::feed(msg, args); + if (type & MDBX_DBG_ASSERT) + abort(); +} + +int testcase::oom_callback(MDBX_env *env, int pid, mdbx_tid_t tid, uint64_t txn, + unsigned gap, int retry) { + + testcase *self = (testcase *)mdbx_env_get_userctx(env); + + if (retry == 0) + log_notice("oom_callback: waitfor pid %u, thread %" PRIuPTR + ", txn #%" PRIu64 ", gap %d", + pid, (size_t)tid, txn, gap); + + if (self->should_continue(true)) { + osal_yield(); + if (retry > 0) + osal_udelay(retry * 100); + return 0 /* always retry */; + } + + return -1; +} + +void testcase::db_prepare() { + log_trace(">> db_prepare"); + assert(!db_guard); + + int mdbx_dbg_opts = MDBX_DBG_ASSERT | MDBX_DBG_JITTER | MDBX_DBG_DUMP; + if (config.params.loglevel <= logging::trace) + mdbx_dbg_opts |= MDBX_DBG_TRACE; + if (config.params.loglevel <= logging::verbose) + mdbx_dbg_opts |= MDBX_DBG_PRINT; + int rc = mdbx_setup_debug(mdbx_dbg_opts, mdbx_logger); + log_info("set mdbx debug-opts: 0x%02x", rc); + + MDBX_env *env = nullptr; + rc = mdbx_env_create(&env); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_env_create()", rc); + + assert(env != nullptr); + db_guard.reset(env); + + rc = mdbx_env_set_userctx(env, this); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_env_set_userctx()", rc); + + rc = mdbx_env_set_maxreaders(env, config.params.max_readers); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_env_set_maxreaders()", rc); + + rc = mdbx_env_set_maxdbs(env, config.params.max_tables); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_env_set_maxdbs()", rc); + + rc = mdbx_env_set_oomfunc(env, testcase::oom_callback); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_env_set_oomfunc()", rc); + + rc = mdbx_env_set_mapsize(env, (size_t)config.params.size); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_env_set_mapsize()", rc); + + log_trace("<< db_prepare"); +} + +void testcase::db_open() { + log_trace(">> db_open"); + + if (!db_guard) + db_prepare(); + int rc = mdbx_env_open(db_guard.get(), config.params.pathname_db.c_str(), + (unsigned)config.params.mode_flags, 0640); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_env_open()", rc); + + log_trace("<< db_open"); +} + +void testcase::db_close() { + log_trace(">> db_close"); + cursor_guard.reset(); + txn_guard.reset(); + db_guard.reset(); + log_trace("<< db_close"); +} + +void testcase::txn_begin(bool readonly, unsigned flags) { + assert((flags & MDBX_RDONLY) == 0); + log_trace(">> txn_begin(%s, 0x%04X)", readonly ? "read-only" : "read-write", + flags); + assert(!txn_guard); + + MDBX_txn *txn = nullptr; + int rc = mdbx_txn_begin(db_guard.get(), nullptr, + readonly ? flags | MDBX_RDONLY : flags, &txn); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_txn_begin()", rc); + txn_guard.reset(txn); + + log_trace("<< txn_begin(%s, 0x%04X)", readonly ? "read-only" : "read-write", + flags); +} + +void testcase::txn_end(bool abort) { + log_trace(">> txn_end(%s)", abort ? "abort" : "commit"); + assert(txn_guard); + + MDBX_txn *txn = txn_guard.release(); + if (abort) { + int rc = mdbx_txn_abort(txn); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_txn_abort()", rc); + } else { + int rc = mdbx_txn_commit(txn); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_txn_commit()", rc); + } + + log_trace("<< txn_end(%s)", abort ? "abort" : "commit"); +} + +void testcase::txn_restart(bool abort, bool readonly, unsigned flags) { + if (txn_guard) + txn_end(abort); + txn_begin(readonly, flags); +} + +bool testcase::wait4start() { + if (config.wait4id) { + log_trace(">> wait4start(%u)", config.wait4id); + assert(!global::singlemode); + int rc = osal_waitfor(config.wait4id); + if (rc) { + log_trace("<< wait4start(%u), failed %s", config.wait4id, + test_strerror(rc)); + return false; + } + } else { + log_trace("== skip wait4start: not needed"); + } + + if (config.params.delaystart) { + int rc = osal_delay(config.params.delaystart); + if (rc) { + log_trace("<< delay(%u), failed %s", config.params.delaystart, + test_strerror(rc)); + return false; + } + } else { + log_trace("== skip delay: not needed"); + } + + return true; +} + +void testcase::kick_progress(bool active) const { + chrono::time now = chrono::now_motonic(); + if (active) { + static int last_point = -1; + int point = (now.fixedpoint >> 29) & 3; + if (point != last_point) { + last.progress_timestamp = now; + fprintf(stderr, "%c\b", "-\\|/"[last_point = point]); + fflush(stderr); + } + } else if (now.fixedpoint - last.progress_timestamp.fixedpoint > + chrono::from_seconds(2).fixedpoint) { + last.progress_timestamp = now; + fprintf(stderr, "%c\b", "@*"[now.utc & 1]); + fflush(stderr); + } +} + +void testcase::report(size_t nops_done) { + assert(nops_done > 0); + if (!nops_done) + return; + + nops_completed += nops_done; + log_verbose("== complete +%" PRIuPTR " iteration, total %" PRIuPTR " done", + nops_done, nops_completed); + + if (global::config::progress_indicator) + kick_progress(true); + + if (config.signal_nops && !signalled && + config.signal_nops <= nops_completed) { + log_trace(">> signal(n-ops %" PRIuPTR ")", nops_completed); + if (!global::singlemode) + osal_broadcast(config.actor_id); + signalled = true; + log_trace("<< signal(n-ops %" PRIuPTR ")", nops_completed); + } +} + +void testcase::signal() { + if (!signalled) { + log_trace(">> signal(forced)"); + if (!global::singlemode) + osal_broadcast(config.actor_id); + signalled = true; + log_trace("<< signal(forced)"); + } +} + +bool testcase::setup() { + db_prepare(); + if (!wait4start()) + return false; + + start_timestamp = chrono::now_motonic(); + return true; +} + +bool testcase::teardown() { + log_trace(">> testcase::teardown"); + signal(); + db_close(); + log_trace("<< testcase::teardown"); + return true; +} + +bool testcase::should_continue(bool check_timeout_only) const { + bool result = true; + + if (config.params.test_duration) { + chrono::time since; + since.fixedpoint = + chrono::now_motonic().fixedpoint - start_timestamp.fixedpoint; + if (since.seconds() >= config.params.test_duration) + result = false; + } + + if (!check_timeout_only && config.params.test_nops && + nops_completed >= config.params.test_nops) + result = false; + + if (result && global::config::progress_indicator) + kick_progress(false); + + return result; +} + +void testcase::fetch_canary() { + mdbx_canary canary_now; + log_trace(">> fetch_canary"); + + int rc = mdbx_canary_get(txn_guard.get(), &canary_now); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_canary_get()", rc); + + if (canary_now.v < last.canary.v) + failure("fetch_canary: %" PRIu64 "(canary-now.v) < %" PRIu64 + "(canary-last.v)", + canary_now.v, last.canary.v); + if (canary_now.y < last.canary.y) + failure("fetch_canary: %" PRIu64 "(canary-now.y) < %" PRIu64 + "(canary-last.y)", + canary_now.y, last.canary.y); + + last.canary = canary_now; + log_trace("<< fetch_canary: db-sequence %" PRIu64 + ", db-sequence.txnid %" PRIu64, + last.canary.y, last.canary.v); +} + +void testcase::update_canary(uint64_t increment) { + mdbx_canary canary_now = last.canary; + + log_trace(">> update_canary: sequence %" PRIu64 " += %" PRIu64, canary_now.y, + increment); + canary_now.y += increment; + + int rc = mdbx_canary_put(txn_guard.get(), &canary_now); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_canary_put()", rc); + + log_trace("<< update_canary: sequence = %" PRIu64, canary_now.y); +} + +MDBX_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"); + + MDBX_dbi handle = 0; + int rc = mdbx_dbi_open(txn_guard.get(), tablename, + (create ? MDBX_CREATE : 0) | config.params.table_flags, + &handle); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_dbi_open()", rc); + + log_trace("<< testcase::db_table_create, handle %u", handle); + return handle; +} + +void testcase::db_table_drop(MDBX_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 != MDBX_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(MDBX_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 != MDBX_SUCCESS)) + failure_perror("mdbx_dbi_close()", rc); + log_trace("<< testcase::db_table_close"); +} + +//----------------------------------------------------------------------------- + +bool test_execute(const actor_config &config) { + const mdbx_pid_t pid = osal_getpid(); + + if (global::singlemode) { + logging::setup(format("single_%s", testcase2str(config.testcase))); + } else { + logging::setup((logging::loglevel)config.params.loglevel, + format("child_%u.%u", config.actor_id, config.space_id)); + log_trace(">> wait4barrier"); + osal_wait4barrier(); + log_trace("<< wait4barrier"); + } + + try { + std::unique_ptr<testcase> test; + switch (config.testcase) { + case ac_hill: + test.reset(new testcase_hill(config, pid)); + break; + case ac_deadread: + test.reset(new testcase_deadread(config, pid)); + break; + case ac_deadwrite: + test.reset(new testcase_deadwrite(config, pid)); + break; + case ac_jitter: + test.reset(new testcase_jitter(config, pid)); + break; + case ac_try: + test.reset(new testcase_try(config, pid)); + break; + default: + test.reset(new testcase(config, pid)); + break; + } + + if (!test->setup()) + log_notice("test setup failed"); + else if (!test->run()) + log_notice("test failed"); + else if (!test->teardown()) + log_notice("test teardown failed"); + else { + log_info("test successed"); + return true; + } + } catch (const std::exception &pipets) { + failure("***** Exception: %s *****", pipets.what()); + } + return false; +} diff --git a/plugins/Dbx_mdbx/src/libmdbx/test/test.h b/plugins/Dbx_mdbx/src/libmdbx/test/test.h new file mode 100644 index 0000000000..67fadebf07 --- /dev/null +++ b/plugins/Dbx_mdbx/src/libmdbx/test/test.h @@ -0,0 +1,203 @@ +/* + * Copyright 2017 Leonid Yuriev <leo@yuriev.ru> + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#pragma once + +#include "base.h" +#include "chrono.h" +#include "config.h" +#include "keygen.h" +#include "log.h" +#include "osal.h" +#include "utils.h" + +bool test_execute(const actor_config &config); +std::string thunk_param(const actor_config &config); +void testcase_setup(const char *casename, actor_params ¶ms, + unsigned &last_space_id); +void configure_actor(unsigned &last_space_id, const actor_testcase testcase, + const char *space_id_cstr, const actor_params ¶ms); +void keycase_setup(const char *casename, actor_params ¶ms); + +namespace global { + +extern const char thunk_param_prefix[]; +extern std::vector<actor_config> actors; +extern std::unordered_map<unsigned, actor_config *> events; +extern std::unordered_map<mdbx_pid_t, actor_config *> pid2actor; +extern std::set<std::string> databases; +extern unsigned nactors; +extern chrono::time start_motonic; +extern chrono::time deadline_motonic; +extern bool singlemode; + +namespace config { +extern unsigned timeout_duration_seconds; +extern bool dump_config; +extern bool cleanup_before; +extern bool cleanup_after; +extern bool failfast; +extern bool progress_indicator; +} /* namespace config */ + +} /* namespace global */ + +//----------------------------------------------------------------------------- + +struct db_deleter : public std::unary_function<void, MDBX_env *> { + void operator()(MDBX_env *env) const { mdbx_env_close(env); } +}; + +struct txn_deleter : public std::unary_function<void, MDBX_txn *> { + void operator()(MDBX_txn *txn) const { + int rc = mdbx_txn_abort(txn); + if (rc) + log_trouble(mdbx_func_, "mdbx_txn_abort()", rc); + } +}; + +struct cursor_deleter : public std::unary_function<void, MDBX_cursor *> { + void operator()(MDBX_cursor *cursor) const { mdbx_cursor_close(cursor); } +}; + +typedef std::unique_ptr<MDBX_env, db_deleter> scoped_db_guard; +typedef std::unique_ptr<MDBX_txn, txn_deleter> scoped_txn_guard; +typedef std::unique_ptr<MDBX_cursor, cursor_deleter> scoped_cursor_guard; + +//----------------------------------------------------------------------------- + +class testcase { +protected: + const actor_config &config; + const mdbx_pid_t pid; + + scoped_db_guard db_guard; + scoped_txn_guard txn_guard; + scoped_cursor_guard cursor_guard; + bool signalled; + + size_t nops_completed; + chrono::time start_timestamp; + keygen::buffer key; + keygen::buffer data; + keygen::maker keyvalue_maker; + + struct { + mdbx_canary canary; + mutable chrono::time progress_timestamp; + } last; + + static int oom_callback(MDBX_env *env, int pid, mdbx_tid_t tid, uint64_t txn, + unsigned gap, int retry); + + void db_prepare(); + void db_open(); + void db_close(); + void txn_begin(bool readonly, unsigned flags = 0); + void txn_end(bool abort); + void txn_restart(bool abort, bool readonly, unsigned flags = 0); + void fetch_canary(); + void update_canary(uint64_t increment); + void kick_progress(bool active) const; + + MDBX_dbi db_table_open(bool create); + void db_table_drop(MDBX_dbi handle); + void db_table_close(MDBX_dbi handle); + + bool wait4start(); + void report(size_t nops_done); + void signal(); + bool should_continue(bool check_timeout_only = false) const; + + void generate_pair(const keygen::serial_t serial, keygen::buffer &out_key, + keygen::buffer &out_value, keygen::serial_t data_age = 0) { + keyvalue_maker.pair(serial, out_key, out_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 & MDBX_RDONLY) ? true : false; + } + +public: + testcase(const actor_config &config, const mdbx_pid_t pid) + : config(config), pid(pid), signalled(false), nops_completed(0) { + start_timestamp.reset(); + memset(&last, 0, sizeof(last)); + } + + virtual bool setup(); + virtual bool run() { return true; } + virtual bool teardown(); + virtual ~testcase() {} +}; + +class testcase_hill : public testcase { + typedef testcase inherited; + +public: + testcase_hill(const actor_config &config, const mdbx_pid_t pid) + : testcase(config, pid) {} + bool setup(); + bool run(); + bool teardown(); +}; + +class testcase_deadread : public testcase { + typedef testcase inherited; + +public: + testcase_deadread(const actor_config &config, const mdbx_pid_t pid) + : testcase(config, pid) {} + bool setup(); + bool run(); + bool teardown(); +}; + +class testcase_deadwrite : public testcase { + typedef testcase inherited; + +public: + testcase_deadwrite(const actor_config &config, const mdbx_pid_t pid) + : testcase(config, pid) {} + bool setup(); + bool run(); + bool teardown(); +}; + +class testcase_jitter : public testcase { + typedef testcase inherited; + +public: + testcase_jitter(const actor_config &config, const mdbx_pid_t pid) + : testcase(config, pid) {} + bool setup(); + bool run(); + bool teardown(); +}; + +class testcase_try : public testcase { + typedef testcase inherited; + +public: + testcase_try(const actor_config &config, const mdbx_pid_t pid) + : testcase(config, pid) {} + bool setup(); + bool run(); + bool teardown(); +}; diff --git a/plugins/Dbx_mdbx/src/libmdbx/test/test.vcxproj b/plugins/Dbx_mdbx/src/libmdbx/test/test.vcxproj new file mode 100644 index 0000000000..3ee13cf8cf --- /dev/null +++ b/plugins/Dbx_mdbx/src/libmdbx/test/test.vcxproj @@ -0,0 +1,202 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\dll.vcxproj"> + <Project>{6d19209b-ece7-4b9c-941c-0aa2b484f199}</Project> + </ProjectReference> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>{30E29CE6-E6FC-4D32-AA07-46A55FAF3A31}</ProjectGuid> + <Keyword>Win32Proj</Keyword> + <RootNamespace>mdbxtest</RootNamespace> + <WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <PlatformToolset>v140</PlatformToolset> + <CharacterSet>MultiByte</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <PlatformToolset>v140</PlatformToolset> + <WholeProgramOptimization>true</WholeProgramOptimization> + <CharacterSet>MultiByte</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <PlatformToolset>v140</PlatformToolset> + <CharacterSet>MultiByte</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <PlatformToolset>v140</PlatformToolset> + <WholeProgramOptimization>true</WholeProgramOptimization> + <CharacterSet>MultiByte</CharacterSet> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Label="Shared"> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LinkIncremental>true</LinkIncremental> + <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir> + <IntDir>$(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\</IntDir> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir> + <IntDir>$(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\</IntDir> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <LinkIncremental>true</LinkIncremental> + <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir> + <IntDir>$(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\</IntDir> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir> + <IntDir>$(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\</IntDir> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <PrecompiledHeader>Use</PrecompiledHeader> + <WarningLevel>Level4</WarningLevel> + <Optimization>Disabled</Optimization> + <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;MDBX_DEBUG=1;%(PreprocessorDefinitions);LIBMDBX_IMPORTS=1</PreprocessorDefinitions> + <SDLCheck>true</SDLCheck> + <PrecompiledHeaderFile>test.h</PrecompiledHeaderFile> + <TreatWarningAsError>true</TreatWarningAsError> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + <AdditionalDependencies>winmm.lib;%(AdditionalDependencies)</AdditionalDependencies> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <ClCompile> + <PrecompiledHeader>Use</PrecompiledHeader> + <WarningLevel>Level4</WarningLevel> + <Optimization>Disabled</Optimization> + <PreprocessorDefinitions>_DEBUG;_CONSOLE;MDBX_DEBUG=1;%(PreprocessorDefinitions);LIBMDBX_IMPORTS=1</PreprocessorDefinitions> + <SDLCheck>true</SDLCheck> + <PrecompiledHeaderFile>test.h</PrecompiledHeaderFile> + <TreatWarningAsError>true</TreatWarningAsError> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + <AdditionalDependencies>winmm.lib;%(AdditionalDependencies)</AdditionalDependencies> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <WarningLevel>Level4</WarningLevel> + <PrecompiledHeader>Use</PrecompiledHeader> + <Optimization>MaxSpeed</Optimization> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);LIBMDBX_IMPORTS=1</PreprocessorDefinitions> + <SDLCheck>true</SDLCheck> + <PrecompiledHeaderFile>test.h</PrecompiledHeaderFile> + <OmitFramePointers>true</OmitFramePointers> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + <GenerateDebugInformation>true</GenerateDebugInformation> + <AdditionalDependencies>winmm.lib;%(AdditionalDependencies)</AdditionalDependencies> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <ClCompile> + <WarningLevel>Level4</WarningLevel> + <PrecompiledHeader>Use</PrecompiledHeader> + <Optimization>MaxSpeed</Optimization> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions);LIBMDBX_IMPORTS=1</PreprocessorDefinitions> + <SDLCheck>true</SDLCheck> + <PrecompiledHeaderFile>test.h</PrecompiledHeaderFile> + <OmitFramePointers>true</OmitFramePointers> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + <GenerateDebugInformation>true</GenerateDebugInformation> + <AdditionalDependencies>winmm.lib;%(AdditionalDependencies)</AdditionalDependencies> + </Link> + </ItemDefinitionGroup> + <ItemGroup> + <ClInclude Include="base.h" /> + <ClInclude Include="chrono.h" /> + <ClInclude Include="config.h" /> + <ClInclude Include="keygen.h" /> + <ClInclude Include="log.h" /> + <ClInclude Include="osal.h" /> + <ClInclude Include="test.h" /> + <ClInclude Include="utils.h" /> + </ItemGroup> + <ItemGroup> + <ClCompile Include="cases.cc" /> + <ClCompile Include="chrono.cc" /> + <ClCompile Include="config.cc" /> + <ClCompile Include="dead.cc" /> + <ClCompile Include="hill.cc" /> + <ClCompile Include="try.cc" /> + <ClCompile Include="jitter.cc" /> + <ClCompile Include="keygen.cc" /> + <ClCompile Include="log.cc" /> + <ClCompile Include="main.cc" /> + <ClCompile Include="osal-windows.cc"> + <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader> + <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader> + <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader> + <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader> + </ClCompile> + <ClCompile Include="test.cc" /> + <ClCompile Include="utils.cc" /> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project> diff --git a/plugins/Dbx_mdbx/src/libmdbx/test/try.cc b/plugins/Dbx_mdbx/src/libmdbx/test/try.cc new file mode 100644 index 0000000000..1deae71d31 --- /dev/null +++ b/plugins/Dbx_mdbx/src/libmdbx/test/try.cc @@ -0,0 +1,37 @@ +#include "test.h" + +bool testcase_try::setup() { + log_trace(">> setup"); + if (!inherited::setup()) + return false; + + log_trace("<< setup"); + return true; +} + +bool testcase_try::run() { + db_open(); + assert(!txn_guard); + + MDBX_txn *txn = nullptr; + MDBX_txn *txn2 = nullptr; + int rc = mdbx_txn_begin(db_guard.get(), nullptr, 0, &txn); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_txn_begin(MDBX_TRYTXN)", rc); + else { + rc = mdbx_txn_begin(db_guard.get(), nullptr, MDBX_TRYTXN, &txn2); + if (unlikely(rc != MDBX_BUSY)) + failure_perror("mdbx_txn_begin(MDBX_TRYTXN)", rc); + } + + txn_guard.reset(txn); + return true; +} + +bool testcase_try::teardown() { + log_trace(">> teardown"); + cursor_guard.release(); + txn_guard.release(); + db_guard.release(); + return inherited::teardown(); +} diff --git a/plugins/Dbx_mdbx/src/libmdbx/test/utils.cc b/plugins/Dbx_mdbx/src/libmdbx/test/utils.cc new file mode 100644 index 0000000000..9a6338cc31 --- /dev/null +++ b/plugins/Dbx_mdbx/src/libmdbx/test/utils.cc @@ -0,0 +1,316 @@ +/* + * Copyright 2017 Leonid Yuriev <leo@yuriev.ru> + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "test.h" +#include <float.h> +#ifdef HAVE_IEEE754_H +#include <ieee754.h> +#endif + +std::string format(const char *fmt, ...) { + va_list ap, ones; + va_start(ap, fmt); + va_copy(ones, ap); +#ifdef _MSC_VER + int needed = _vscprintf(fmt, ap); +#else + int needed = vsnprintf(nullptr, 0, fmt, ap); +#endif + assert(needed >= 0); + va_end(ap); + std::string result; + result.reserve((size_t)needed + 1); + result.resize((size_t)needed, '\0'); + int actual = vsnprintf((char *)result.data(), result.capacity(), fmt, ones); + assert(actual == needed); + (void)actual; + va_end(ones); + return result; +} + +std::string data2hex(const void *ptr, size_t bytes, simple_checksum &checksum) { + std::string result; + if (bytes > 0) { + const uint8_t *data = (const uint8_t *)ptr; + checksum.push(data, bytes); + result.reserve(bytes * 2); + const uint8_t *const end = data + bytes; + do { + char h = *data >> 4; + char l = *data & 15; + result.push_back((l < 10) ? l + '0' : l - 10 + 'a'); + result.push_back((h < 10) ? h + '0' : h - 10 + 'a'); + } while (++data < end); + } + assert(result.size() == bytes * 2); + return result; +} + +bool hex2data(const char *hex_begin, const char *hex_end, void *ptr, + size_t bytes, simple_checksum &checksum) { + if (bytes * 2 != (size_t)(hex_end - hex_begin)) + return false; + + uint8_t *data = (uint8_t *)ptr; + for (const char *hex = hex_begin; hex != hex_end; hex += 2, ++data) { + unsigned l = hex[0], h = hex[1]; + + if (l >= '0' && l <= '9') + l = l - '0'; + else if (l >= 'A' && l <= 'F') + l = l - 'A' + 10; + else if (l >= 'a' && l <= 'f') + l = l - 'a' + 10; + else + return false; + + if (h >= '0' && h <= '9') + h = h - '0'; + else if (h >= 'A' && h <= 'F') + h = h - 'A' + 10; + else if (h >= 'a' && h <= 'f') + h = h - 'a' + 10; + else + return false; + + uint32_t c = l + (h << 4); + checksum.push(c); + *data = (uint8_t)c; + } + return true; +} + +//----------------------------------------------------------------------------- + +#ifdef __mips__ +static uint64_t *mips_tsc_addr; + +__cold static void mips_rdtsc_init() { + int mem_fd = open("/dev/mem", O_RDONLY | O_SYNC, 0); + HIPPEUS_ENSURE(mem_fd >= 0); + + mips_tsc_addr = mmap(nullptr, pagesize, PROT_READ, MAP_SHARED, mem_fd, + 0x10030000 /* MIPS_ZBUS_TIMER */); + close(mem_fd); +} +#endif /* __mips__ */ + +uint64_t entropy_ticks(void) { +#if defined(__GNUC__) || defined(__clang__) +#if defined(__ia64__) + uint64_t ticks; + __asm __volatile("mov %0=ar.itc" : "=r"(ticks)); + return ticks; +#elif defined(__hppa__) + uint64_t ticks; + __asm __volatile("mfctl 16, %0" : "=r"(ticks)); + return ticks; +#elif defined(__s390__) + uint64_t ticks; + __asm __volatile("stck 0(%0)" : : "a"(&(ticks)) : "memory", "cc"); + return ticks; +#elif defined(__alpha__) + uint64_t ticks; + __asm __volatile("rpcc %0" : "=r"(ticks)); + return ticks; +#elif defined(__sparc_v9__) + uint64_t ticks; + __asm __volatile("rd %%tick, %0" : "=r"(ticks)); + return ticks; +#elif defined(__powerpc64__) || defined(__ppc64__) + uint64_t ticks; + __asm __volatile("mfspr %0, 268" : "=r"(ticks)); + return ticks; +#elif defined(__ppc__) || defined(__powerpc__) + unsigned tbl, tbu; + + /* LY: Here not a problem if a high-part (tbu) + * would been updated during reading. */ + __asm __volatile("mftb %0" : "=r"(tbl)); + __asm __volatile("mftbu %0" : "=r"(tbu)); + + return (((uin64_t)tbu0) << 32) | tbl; +#elif defined(__mips__) + if (mips_tsc_addr != MAP_FAILED) { + if (unlikely(!mips_tsc_addr)) { + static pthread_once_t is_initialized = PTHREAD_ONCE_INIT; + int rc = pthread_once(&is_initialized, mips_rdtsc_init); + if (unlikely(rc)) + failure_perror("pthread_once()", rc); + } + if (mips_tsc_addr != MAP_FAILED) + return *mips_tsc_addr; + } +#elif defined(__x86_64__) || defined(__i386__) +#if __GNUC_PREREQ(4, 7) || __has_builtin(__builtin_ia32_rdtsc) + return __builtin_ia32_rdtsc(); +#else + unsigned lo, hi; + + /* LY: Using the "a" and "d" constraints is important for correct code. */ + __asm __volatile("rdtsc" : "=a"(lo), "=d"(hi)); + + return (((uint64_t)hi) << 32) + lo; +#endif +#endif /* arch selector */ + +#elif defined(_M_IX86) || defined(_M_X64) + return __rdtsc(); +#elif defined(_WIN32) || defined(_WIN64) || defined(_WINDOWS) + LARGE_INTEGER PerformanceCount; + if (QueryPerformanceCounter(&PerformanceCount)) + return PerformanceCount.QuadPart; + return GetTickCount64(); +#else + struct timespec ts; +#if defined(CLOCK_MONOTONIC_COARSE) + clockid_t clock = CLOCK_MONOTONIC_COARSE; +#elif defined(CLOCK_MONOTONIC_RAW) + clockid_t clock = CLOCK_MONOTONIC_RAW; +#else + clockid_t clock = CLOCK_MONOTONIC; +#endif + int rc = clock_gettime(clock, &ts); + if (unlikely(rc)) + failure_perror("clock_gettime()", rc); + + return (((uint64_t)ts.tv_sec) << 32) + ts.tv_nsec; +#endif +} + +//----------------------------------------------------------------------------- + +static __inline uint64_t bleach64(uint64_t 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(2175734609)) >> 32); +} + +uint64_t prng64_careless(uint64_t &state) { + state = state * UINT64_C(6364136223846793005) + 1; + return state; +} + +uint64_t prng64_white(uint64_t &state) { + state = state * UINT64_C(6364136223846793005) + UINT64_C(1442695040888963407); + return bleach64(state); +} + +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) { +#ifdef IEEE754_DOUBLE_BIAS + ieee754_double r; + r.ieee.negative = 0; + r.ieee.exponent = IEEE754_DOUBLE_BIAS; + r.ieee.mantissa0 = (unsigned)(salt >> 32); + r.ieee.mantissa1 = (unsigned)salt; + return r.d; +#else + const uint64_t top = (UINT64_C(1) << DBL_MANT_DIG) - 1; + const double scale = 1.0 / (double)top; + return (salt & top) * scale; +#endif +} + +double double_from_upper(uint64_t salt) { +#ifdef IEEE754_DOUBLE_BIAS + ieee754_double r; + r.ieee.negative = 0; + r.ieee.exponent = IEEE754_DOUBLE_BIAS; + salt >>= 64 - DBL_MANT_DIG; + r.ieee.mantissa0 = (unsigned)(salt >> 32); + r.ieee.mantissa1 = (unsigned)salt; + return r.d; +#else + const uint64_t top = (UINT64_C(1) << DBL_MANT_DIG) - 1; + const double scale = 1.0 / (double)top; + return (salt >> (64 - DBL_MANT_DIG)) * scale; +#endif +} + +bool flipcoin() { return bleach32((uint32_t)entropy_ticks()) & 1; } + +bool jitter(unsigned probability_percent) { + const uint32_t top = UINT32_MAX - UINT32_MAX % 100; + uint32_t dice, edge = (top) / 100 * probability_percent; + do + dice = bleach32((uint32_t)entropy_ticks()); + while (dice >= top); + return dice < edge; +} + +void jitter_delay(bool extra) { + unsigned dice = entropy_white() & 3; + if (dice == 0) { + log_trace("== jitter.no-delay"); + } else { + log_trace(">> jitter.delay: dice %u", dice); + do { + cpu_relax(); + memory_barrier(); + cpu_relax(); + if (dice > 1) { + osal_yield(); + cpu_relax(); + if (dice > 2) { + unsigned us = entropy_white() & + (extra ? 0xfffff /* 1.05 s */ : 0x3ff /* 1 ms */); + log_trace("== jitter.delay: %0.6f", us / 1000000.0); + osal_udelay(us); + } + } + } while (flipcoin()); + log_trace("<< jitter.delay: dice %u", dice); + } +} diff --git a/plugins/Dbx_mdbx/src/libmdbx/test/utils.h b/plugins/Dbx_mdbx/src/libmdbx/test/utils.h new file mode 100644 index 0000000000..b7fd62eb09 --- /dev/null +++ b/plugins/Dbx_mdbx/src/libmdbx/test/utils.h @@ -0,0 +1,363 @@ +/* + * Copyright 2017 Leonid Yuriev <leo@yuriev.ru> + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#pragma once +#include "base.h" + +#if !defined(__BYTE_ORDER__) || !defined(__ORDER_LITTLE_ENDIAN__) || \ + !defined(__ORDER_BIG_ENDIAN__) +#ifndef _MSC_VER +#include <sys/param.h> /* for endianness */ +#endif +#if defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && defined(__BIG_ENDIAN) +#define __ORDER_LITTLE_ENDIAN__ __LITTLE_ENDIAN +#define __ORDER_BIG_ENDIAN__ __BIG_ENDIAN +#define __BYTE_ORDER__ __BYTE_ORDER +#else +#define __ORDER_LITTLE_ENDIAN__ 1234 +#define __ORDER_BIG_ENDIAN__ 4321 +#if defined(__LITTLE_ENDIAN__) || defined(_LITTLE_ENDIAN) || \ + defined(__ARMEL__) || defined(__THUMBEL__) || defined(__AARCH64EL__) || \ + defined(__MIPSEL__) || defined(_MIPSEL) || defined(__MIPSEL) || \ + defined(__i386) || defined(__x86_64__) || defined(_M_IX86) || \ + defined(_M_X64) || defined(i386) || defined(_X86_) || defined(__i386__) || \ + defined(_X86_64_) || defined(_M_ARM) || defined(_M_ARM64) || \ + defined(__e2k__) +#define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__ +#elif defined(__BIG_ENDIAN__) || defined(_BIG_ENDIAN) || defined(__ARMEB__) || \ + defined(__THUMBEB__) || defined(__AARCH64EB__) || defined(__MIPSEB__) || \ + defined(_MIPSEB) || defined(__MIPSEB) || defined(_M_IA64) +#define __BYTE_ORDER__ __ORDER_BIG_ENDIAN__ +#else +#error __BYTE_ORDER__ should be defined. +#endif +#endif +#endif + +#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ && \ + __BYTE_ORDER__ != __ORDER_BIG_ENDIAN__ +#error Unsupported byte order. +#endif + +#if __GNUC_PREREQ(4, 4) || defined(__clang__) +#define bswap64(v) __builtin_bswap64(v) +#define bswap32(v) __builtin_bswap32(v) +#if __GNUC_PREREQ(4, 8) || __has_builtin(__builtin_bswap16) +#define bswap16(v) __builtin_bswap16(v) +#endif + +#elif defined(_MSC_VER) + +#if _MSC_FULL_VER < 190024215 +#pragma message( \ + "It is recommended to use Visual Studio 2015 (MSC 19.0) or newer.") +#endif + +#define bswap64(v) _byteswap_uint64(v) +#define bswap32(v) _byteswap_ulong(v) +#define bswap16(v) _byteswap_ushort(v) +#define rot64(v, s) _rotr64(v, s) +#define rot32(v, s) _rotr(v, s) + +#if defined(_M_ARM64) || defined(_M_X64) || defined(_M_IA64) +#pragma intrinsic(_umul128) +#define mul_64x64_128(a, b, ph) _umul128(a, b, ph) +#pragma intrinsic(__umulh) +#define mul_64x64_high(a, b) __umulh(a, b) +#endif + +#if defined(_M_IX86) +#pragma intrinsic(__emulu) +#define mul_32x32_64(a, b) __emulu(a, b) +#elif defined(_M_ARM) +#define mul_32x32_64(a, b) _arm_umull(a, b) +#endif + +#endif /* compiler */ + +#ifndef bswap64 +#ifdef __bswap_64 +#define bswap64(v) __bswap_64(v) +#else +static __inline uint64_t bswap64(uint64_t v) { + 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 */ + +#ifndef bswap32 +#ifdef __bswap_32 +#define bswap32(v) __bswap_32(v) +#else +static __inline uint32_t bswap32(uint32_t v) { + return v << 24 | v >> 24 | ((v << 8) & UINT32_C(0x00ff0000)) | + ((v >> 8) & UINT32_C(0x0000ff00)); +} +#endif +#endif /* bswap32 */ + +#ifndef bswap16 +#ifdef __bswap_16 +#define bswap16(v) __bswap_16(v) +#else +static __inline uint16_t bswap16(uint16_t v) { return v << 8 | v >> 8; } +#endif +#endif /* bswap16 */ + +#define is_byteorder_le() (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) +#define is_byteorder_be() (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + +#ifndef htole16 +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define htobe16(v) bswap16(v) +#define htole16(v) (v) +#define be16toh(v) bswap16(v) +#define le16toh(v) (v) +#else +#define htobe16(v) (v) +#define htole16(v) bswap16(v) +#define be16toh(v) (v) +#define le16toh(v) bswap16(v) +#endif +#endif /* htole16 */ + +#ifndef htole32 +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define htobe32(v) bswap32(v) +#define htole32(v) (v) +#define be32toh(v) bswap32(v) +#define le32toh(v) (v) +#else +#define htobe32(v) (v) +#define htole32(v) bswap32(v) +#define be32toh(v) (v) +#define le32toh(v) bswap32(v) +#endif +#endif /* htole32 */ + +#ifndef htole64 +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define htobe64(v) bswap64(v) +#define htole64(v) (v) +#define be64toh(v) bswap64(v) +#define le64toh(v) (v) +#else +#define htobe64(v) (v) +#define htole64(v) bswap_64(v) +#define be64toh(v) (v) +#define le64toh(v) bswap_64(v) +#endif +#endif /* htole64 */ + +namespace unaligned { + +template <typename T> static __inline T load(const void *ptr) { +#if defined(_MSC_VER) && \ + (defined(_M_ARM64) || defined(_M_X64) || defined(_M_IA64)) + return *(const T __unaligned *)ptr; +#elif UNALIGNED_OK + return *(const T *)ptr; +#else + T local; +#if defined(__GNUC__) || defined(__clang__) + __builtin_memcpy(&local, (const T *)ptr, sizeof(T)); +#else + memcpy(&local, (const T *)ptr, sizeof(T)); +#endif /* __GNUC__ || __clang__ */ + return local; +#endif /* UNALIGNED_OK */ +} + +template <typename T> static __inline void store(void *ptr, const T &value) { +#if defined(_MSC_VER) && \ + (defined(_M_ARM64) || defined(_M_X64) || defined(_M_IA64)) + *((T __unaligned *)ptr) = value; +#elif UNALIGNED_OK + *(volatile T *)ptr = value; +#else +#if defined(__GNUC__) || defined(__clang__) + __builtin_memcpy(ptr, &value, sizeof(T)); +#else + memcpy(ptr, &value, sizeof(T)); +#endif /* __GNUC__ || __clang__ */ +#endif /* UNALIGNED_OK */ +} + +} /* namespace unaligned */ + +//----------------------------------------------------------------------------- + +#ifndef rot64 +static __inline uint64_t rot64(uint64_t v, unsigned s) { + return (v >> s) | (v << (64 - s)); +} +#endif /* rot64 */ + +#ifndef mul_32x32_64 +static __inline uint64_t mul_32x32_64(uint32_t a, uint32_t b) { + return a * (uint64_t)b; +} +#endif /* mul_32x32_64 */ + +#ifndef mul_64x64_128 + +static __inline unsigned add_with_carry(uint64_t *sum, uint64_t addend) { + *sum += addend; + return (*sum < addend) ? 1u : 0u; +} + +static __inline uint64_t mul_64x64_128(uint64_t a, uint64_t b, uint64_t *h) { +#if defined(__SIZEOF_INT128__) || \ + (defined(_INTEGRAL_MAX_BITS) && _INTEGRAL_MAX_BITS >= 128) + __uint128_t r = (__uint128_t)a * (__uint128_t)b; + /* modern GCC could nicely optimize this */ + *h = r >> 64; + return r; +#elif defined(mul_64x64_high) + *h = mul_64x64_high(a, b); + return a * b; +#else + /* performs 64x64 to 128 bit multiplication */ + uint64_t ll = mul_32x32_64((uint32_t)a, (uint32_t)b); + uint64_t lh = mul_32x32_64(a >> 32, (uint32_t)b); + uint64_t hl = mul_32x32_64((uint32_t)a, b >> 32); + *h = mul_32x32_64(a >> 32, b >> 32) + (lh >> 32) + (hl >> 32) + + add_with_carry(&ll, lh << 32) + add_with_carry(&ll, hl << 32); + return ll; +#endif +} + +#endif /* mul_64x64_128() */ + +#ifndef mul_64x64_high +static __inline uint64_t mul_64x64_high(uint64_t a, uint64_t b) { + uint64_t h; + mul_64x64_128(a, b, &h); + return h; +} +#endif /* mul_64x64_high */ + +static __inline bool is_power2(size_t x) { return (x & (x - 1)) == 0; } + +static __inline size_t roundup2(size_t value, size_t granularity) { + assert(is_power2(granularity)); + return (value + granularity - 1) & ~(granularity - 1); +} + +//----------------------------------------------------------------------------- + +static __inline void memory_barrier(void) { +#if __has_extension(c_atomic) || __has_extension(cxx_atomic) + __c11_atomic_thread_fence(__ATOMIC_SEQ_CST); +#elif defined(__ATOMIC_SEQ_CST) + __atomic_thread_fence(__ATOMIC_SEQ_CST); +#elif defined(__clang__) || defined(__GNUC__) + __sync_synchronize(); +#elif defined(_MSC_VER) + MemoryBarrier(); +#elif defined(__INTEL_COMPILER) /* LY: Intel Compiler may mimic GCC and MSC */ +#if defined(__ia64__) || defined(__ia64) || defined(_M_IA64) + __mf(); +#elif defined(__i386__) || defined(__x86_64__) + _mm_mfence(); +#else +#error "Unknown target for Intel Compiler, please report to us." +#endif +#elif defined(__SUNPRO_C) || defined(__sun) || defined(sun) + __machine_rw_barrier(); +#elif (defined(_HPUX_SOURCE) || defined(__hpux) || defined(__HP_aCC)) && \ + (defined(HP_IA64) || defined(__ia64)) + _Asm_mf(); +#elif defined(_AIX) || defined(__ppc__) || defined(__powerpc__) || \ + defined(__ppc64__) || defined(__powerpc64__) + __lwsync(); +#else +#error "Could not guess the kind of compiler, please report to us." +#endif +} + +static __inline void cpu_relax() { +#if defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || \ + defined(_M_X64) + _mm_pause(); +#elif defined(_WIN32) || defined(_WIN64) || defined(_WINDOWS) || \ + defined(YieldProcessor) + YieldProcessor(); +#else +/* nope */ +#endif +} + +//----------------------------------------------------------------------------- + +struct simple_checksum { + uint64_t value; + + simple_checksum() : value(0) {} + + void push(uint32_t data) { + value += data * UINT64_C(9386433910765580089) + 1; + value ^= value >> 41; + } + + void push(uint64_t data) { + push((uint32_t)data); + push((uint32_t)(data >> 32)); + } + + void push(bool data) { push(data ? UINT32_C(0x780E) : UINT32_C(0xFA18E)); } + + void push(const void *ptr, size_t bytes) { + const uint8_t *data = (const uint8_t *)ptr; + for (size_t i = 0; i < bytes; ++i) + push((uint32_t)data[i]); + } + + void push(const double &data) { push(&data, sizeof(double)); } + + void push(const char *cstr) { push(cstr, strlen(cstr)); } + + void push(const std::string &str) { push(str.data(), str.size()); } + +#if defined(_WIN32) || defined(_WIN64) || defined(_WINDOWS) + void push(const HANDLE &handle) { push(&handle, sizeof(handle)); } +#endif /* _WINDOWS */ +}; + +std::string data2hex(const void *ptr, size_t bytes, simple_checksum &checksum); +bool hex2data(const char *hex_begin, const char *hex_end, void *ptr, + size_t bytes, simple_checksum &checksum); + +std::string format(const char *fmt, ...); + +uint64_t entropy_ticks(void); +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); +void jitter_delay(bool extra = false); |