20 #include <ripple/basics/contract.h>
21 #include <ripple/beast/clock/basic_seconds_clock.h>
22 #include <ripple/beast/core/LexicalCast.h>
23 #include <ripple/beast/rfc2616.h>
24 #include <ripple/beast/unit_test.h>
25 #include <ripple/nodestore/impl/codec.h>
26 #include <boost/beast/core/string.hpp>
27 #include <boost/regex.hpp>
32 #include <nudb/create.hpp>
33 #include <nudb/detail/format.hpp>
34 #include <nudb/xxhasher.hpp>
37 #include <ripple/unity/rocksdb.h>
91 template <
class Rep,
class Period>
109 os << round<nanoseconds>(d).count();
125 os << round<microseconds>(d).count();
141 os << round<milliseconds>(d).count();
157 os << round<seconds>(d).count();
173 os << round<minutes>(d).count();
180 template <
class Period,
class Rep>
203 bool estimate_ =
false;
215 auto const now = clock_type::now();
219 auto const elapsed = now - start_;
230 auto const rate = elapsed.count() / double(work);
233 log <<
"Remaining: " <<
detail::fmtdur(remain) <<
" (" << work <<
" of "
235 << (work - prev_) <<
" in " <<
detail::fmtdur(now - report_) <<
")";
244 log <<
"Total time: " <<
detail::fmtdur(clock_type::now() - start_);
252 static boost::regex
const re1(
255 "([a-zA-Z][_a-zA-Z0-9]*)"
262 boost::regex_constants::optimize);
265 for (
auto const& kv : v)
268 if (!boost::regex_match(kv, m, re1))
269 Throw<std::runtime_error>(
"invalid parameter " + kv);
270 auto const result = map.
emplace(m[1], m[2]);
272 Throw<std::runtime_error>(
"duplicate parameter " + m[1]);
279 #if RIPPLE_ROCKSDB_AVAILABLE
281 class import_test :
public beast::unit_test::suite
287 testcase(beast::unit_test::abort_on_fail) << arg();
289 using namespace nudb;
290 using namespace nudb::detail;
294 bool usage = args.empty();
296 if (!usage && args.find(
"from") == args.end())
298 log <<
"Missing parameter: from";
301 if (!usage && args.find(
"to") == args.end())
303 log <<
"Missing parameter: to";
306 if (!usage && args.find(
"buffer") == args.end())
308 log <<
"Missing parameter: buffer";
315 <<
"--unittest-arg=from=<from>,to=<to>,buffer=<buffer>\n"
316 <<
"from: RocksDB database to import from\n"
317 <<
"to: NuDB database to import to\n"
318 <<
"buffer: Buffer size (bigger is faster)\n"
319 <<
"NuDB database must not already exist.";
328 auto const from_path = args.at(
"from");
329 auto const to_path = args.at(
"to");
331 using hash_type = nudb::xxhasher;
332 auto const bulk_size = 64 * 1024 * 1024;
333 float const load_factor = 0.5;
335 auto const dp = to_path +
".dat";
336 auto const kp = to_path +
".key";
340 log <<
"from: " << from_path
350 rocksdb::Options options;
351 options.create_if_missing =
false;
352 options.max_open_files = 2000;
353 rocksdb::DB* pdb =
nullptr;
355 rocksdb::DB::OpenForReadOnly(options, from_path, &pdb);
357 Throw<std::runtime_error>(
358 "Can't open '" + from_path +
"': " +
status.ToString());
364 dh.version = currentVersion;
371 df.create(file_mode::append, dp, ec);
373 Throw<nudb::system_error>(ec);
374 bulk_writer<native_file> dw(df, 0, bulk_size);
377 auto os = dw.prepare(dat_file_header::size, ec);
379 Throw<nudb::system_error>(ec);
382 rocksdb::ReadOptions options;
383 options.verify_checksums =
false;
384 options.fill_cache =
false;
388 for (it->SeekToFirst(); it->Valid(); it->Next())
390 if (it->key().size() != 32)
391 Throw<std::runtime_error>(
392 "Unexpected key size " +
394 void const*
const key = it->key().data();
395 void const*
const data = it->value().data();
396 auto const size = it->value().size();
406 BEAST_EXPECT(
check.second == size);
411 auto os = dw.prepare(
412 field<uint48_t>::size +
417 Throw<nudb::system_error>(ec);
418 write<uint48_t>(os,
out.second);
425 Throw<nudb::system_error>(ec);
428 log <<
"Import data: "
430 auto const df_size = df.size(ec);
432 Throw<nudb::system_error>(ec);
435 kh.version = currentVersion;
437 kh.appnum = dh.appnum;
439 kh.salt = make_salt();
440 kh.pepper = pepper<hash_type>(kh.salt);
441 kh.block_size = block_size(kp);
442 kh.load_factor = std::min<std::size_t>(65536.0 * load_factor, 65535);
444 std::ceil(nitems / (bucket_capacity(kh.block_size) * load_factor));
445 kh.modulus = ceil_pow2(kh.buckets);
447 kf.create(file_mode::append, kp, ec);
449 Throw<nudb::system_error>(ec);
450 buffer buf(kh.block_size);
453 ostream os(buf.get(), kh.block_size);
455 kf.write(0, buf.get(), kh.block_size, ec);
457 Throw<nudb::system_error>(ec);
463 std::max<std::size_t>(1, buffer_size / kh.block_size);
464 buf.reserve(buckets * kh.block_size);
465 auto const passes = (kh.buckets + buckets - 1) / buckets;
466 log <<
"items: " << nitems
476 progress p(df_size * passes);
478 for (
std::size_t b0 = 0; b0 < kh.buckets; b0 += buckets)
480 auto const b1 =
std::min(b0 + buckets, kh.buckets);
482 auto const bn = b1 - b0;
486 bucket b(kh.block_size, buf.get() + i * kh.block_size, empty);
490 bulk_reader<native_file> r(
491 df, dat_file_header::size, df_size, bulk_size);
494 auto const offset = r.offset();
497 auto is = r.prepare(field<uint48_t>::size, ec);
499 Throw<nudb::system_error>(ec);
500 read<uint48_t>(is, size);
509 Throw<nudb::system_error>(ec);
511 auto const h = hash<hash_type>(key, kh.key_size, kh.salt);
512 auto const n = bucket_index(h, kh.buckets, kh.modulus);
513 p(log, npass * df_size + r.offset());
514 if (n < b0 || n >= b1)
517 kh.block_size, buf.get() + (n - b0) * kh.block_size);
518 maybe_spill(b, dw, ec);
520 Throw<nudb::system_error>(ec);
521 b.insert(offset, size, h);
527 is = r.prepare(field<std::uint16_t>::size, ec);
529 Throw<nudb::system_error>(ec);
530 read<std::uint16_t>(is, size);
533 Throw<nudb::system_error>(ec);
537 (b0 + 1) * kh.block_size, buf.get(), bn * kh.block_size, ec);
539 Throw<nudb::system_error>(ec);
544 Throw<nudb::system_error>(ec);