rippled
Loading...
Searching...
No Matches
Timing_test.cpp
1#include <test/nodestore/TestBase.h>
2#include <test/unit_test/SuiteJournal.h>
3
4#include <xrpl/basics/BasicConfig.h>
5#include <xrpl/basics/ByteUtilities.h>
6#include <xrpl/basics/safe_cast.h>
7#include <xrpl/beast/unit_test.h>
8#include <xrpl/beast/unit_test/thread.h>
9#include <xrpl/beast/utility/temp_dir.h>
10#include <xrpl/beast/xor_shift_engine.h>
11#include <xrpl/nodestore/DummyScheduler.h>
12#include <xrpl/nodestore/Manager.h>
13
14#include <boost/algorithm/string.hpp>
15
16#include <algorithm>
17#include <atomic>
18#include <chrono>
19#include <iterator>
20#include <limits>
21#include <random>
22#include <sstream>
23#include <stdexcept>
24#include <type_traits>
25#include <utility>
26
27#ifndef NODESTORE_TIMING_DO_VERIFY
28#define NODESTORE_TIMING_DO_VERIFY 0
29#endif
30
31namespace xrpl {
32namespace NodeStore {
33
35make_Backend(Section const& config, Scheduler& scheduler, beast::Journal journal)
36{
37 return Manager::instance().make_Backend(config, megabytes(4), scheduler, journal);
38}
39
40// Fill memory with random bits
41template <class Generator>
42static void
43rngcpy(void* buffer, std::size_t bytes, Generator& g)
44{
45 using result_type = typename Generator::result_type;
46 while (bytes >= sizeof(result_type))
47 {
48 auto const v = g();
49 memcpy(buffer, &v, sizeof(v));
50 buffer = reinterpret_cast<std::uint8_t*>(buffer) + sizeof(v);
51 bytes -= sizeof(v);
52 }
53
54 if (bytes > 0)
55 {
56 auto const v = g();
57 memcpy(buffer, &v, bytes);
58 }
59}
60
61// Instance of node factory produces a deterministic sequence
62// of random NodeObjects within the given
64{
65private:
66 enum { minLedger = 1, maxLedger = 1000000, minSize = 250, maxSize = 1250 };
67
72
73public:
74 explicit Sequence(std::uint8_t prefix)
75 : prefix_(prefix)
76 // uniform distribution over hotLEDGER - hotTRANSACTION_NODE
77 // but exclude hotTRANSACTION = 2 (removed)
78 , d_type_({1, 1, 0, 1, 1})
80 {
81 }
82
83 // Returns the n-th key
86 {
87 gen_.seed(n + 1);
88 uint256 result;
89 rngcpy(&*result.begin(), result.size(), gen_);
90 return result;
91 }
92
93 // Returns the n-th complete NodeObject
96 {
97 gen_.seed(n + 1);
99 auto const data = static_cast<std::uint8_t*>(&*key.begin());
100 *data = prefix_;
101 rngcpy(data + 1, key.size() - 1, gen_);
102 Blob value(d_size_(gen_));
103 rngcpy(&value[0], value.size(), gen_);
105 safe_cast<NodeObjectType>(d_type_(gen_)), std::move(value), key);
106 }
107
108 // returns a batch of NodeObjects starting at n
109 void
111 {
112 b.clear();
113 b.reserve(size);
114 while ((size--) != 0u)
115 b.emplace_back(obj(n++));
116 }
117};
118
119//----------------------------------------------------------------------------------
120
122{
123public:
124 enum {
125 // percent of fetches for missing nodes
127 };
128
130#ifndef NDEBUG
132#else
133 std::size_t const default_items = 100000; // release
134#endif
135
138
144
145 static std::string
146 to_string(Section const& config)
147 {
148 std::string s;
149 for (auto iter = config.begin(); iter != config.end(); ++iter)
150 s += (iter != config.begin() ? "," : "") + iter->first + "=" + iter->second;
151 return s;
152 }
153
154 static std::string
156 {
158 ss << std::fixed << std::setprecision(3) << (d.count() / 1000.) << "s";
159 return ss.str();
160 }
161
162 static Section
164 {
165 Section section;
167 boost::split(v, s, boost::algorithm::is_any_of(","));
168 section.append(v);
169 return section;
170 }
171
172 //--------------------------------------------------------------------------
173
174 // Workaround for GCC's parameter pack expansion in lambdas
175 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47226
176 template <class Body>
178 {
179 private:
182
183 public:
187
188 template <class... Args>
189 void
190 operator()(Args&&... args)
191 {
192 Body body(args...);
193 for (;;)
194 {
195 auto const i = c_++;
196 if (i >= n_)
197 break;
198 body(i);
199 }
200 }
201 };
202
203 /* Execute parallel-for loop.
204
205 Constructs `number_of_threads` instances of `Body`
206 with `args...` parameters and runs them on individual threads
207 with unique loop indexes in the range [0, n).
208 */
209 template <class Body, class... Args>
210 void
211 parallel_for(std::size_t const n, std::size_t number_of_threads, Args const&... args)
212 {
215 t.reserve(number_of_threads);
216 for (std::size_t id = 0; id < number_of_threads; ++id)
217 t.emplace_back(*this, parallel_for_lambda<Body>(n, c), args...);
218 for (auto& _ : t)
219 _.join();
220 }
221
222 template <class Body, class... Args>
223 void
224 parallel_for_id(std::size_t const n, std::size_t number_of_threads, Args const&... args)
225 {
228 t.reserve(number_of_threads);
229 for (std::size_t id = 0; id < number_of_threads; ++id)
230 t.emplace_back(*this, parallel_for_lambda<Body>(n, c), id, args...);
231 for (auto& _ : t)
232 _.join();
233 }
234
235 //--------------------------------------------------------------------------
236
237 // Insert only
238 void
239 do_insert(Section const& config, Params const& params, beast::Journal journal)
240 {
241 DummyScheduler scheduler;
242 auto backend = make_Backend(config, scheduler, journal);
243 BEAST_EXPECT(backend != nullptr);
244 backend->open();
245
246 class Body
247 {
248 private:
249 suite& suite_;
250 Backend& backend_;
251 Sequence seq_;
252
253 public:
254 explicit Body(suite& s, Backend& backend) : suite_(s), backend_(backend), seq_(1)
255 {
256 }
257
258 void
259 operator()(std::size_t i)
260 {
261 try
262 {
263 backend_.store(seq_.obj(i));
264 }
265 catch (std::exception const& e)
266 {
267 suite_.fail(e.what());
268 }
269 }
270 };
271
272 try
273 {
274 parallel_for<Body>(params.items, params.threads, std::ref(*this), std::ref(*backend));
275 }
276 catch (std::exception const&)
277 {
278#if NODESTORE_TIMING_DO_VERIFY
279 backend->verify();
280#endif
281 Rethrow();
282 }
283 backend->close();
284 }
285
286 // Fetch existing keys
287 void
288 do_fetch(Section const& config, Params const& params, beast::Journal journal)
289 {
290 DummyScheduler scheduler;
291 auto backend = make_Backend(config, scheduler, journal);
292 BEAST_EXPECT(backend != nullptr);
293 backend->open();
294
295 class Body
296 {
297 private:
298 suite& suite_;
299 Backend& backend_;
300 Sequence seq1_;
303
304 public:
305 Body(std::size_t id, suite& s, Params const& params, Backend& backend)
306 : suite_(s), backend_(backend), seq1_(1), gen_(id + 1), dist_(0, params.items - 1)
307 {
308 }
309
310 void
311 operator()(std::size_t i)
312 {
313 try
314 {
317 obj = seq1_.obj(dist_(gen_));
318 backend_.fetch(obj->getHash(), &result);
319 suite_.expect(result && isSame(result, obj));
320 }
321 catch (std::exception const& e)
322 {
323 suite_.fail(e.what());
324 }
325 }
326 };
327 try
328 {
329 parallel_for_id<Body>(
330 params.items,
331 params.threads,
332 std::ref(*this),
333 std::ref(params),
334 std::ref(*backend));
335 }
336 catch (std::exception const&)
337 {
338#if NODESTORE_TIMING_DO_VERIFY
339 backend->verify();
340#endif
341 Rethrow();
342 }
343 backend->close();
344 }
345
346 // Perform lookups of non-existent keys
347 void
348 do_missing(Section const& config, Params const& params, beast::Journal journal)
349 {
350 DummyScheduler scheduler;
351 auto backend = make_Backend(config, scheduler, journal);
352 BEAST_EXPECT(backend != nullptr);
353 backend->open();
354
355 class Body
356 {
357 private:
358 suite& suite_;
359 // Params const& params_;
360 Backend& backend_;
361 Sequence seq2_;
364
365 public:
366 Body(std::size_t id, suite& s, Params const& params, Backend& backend)
367 : suite_(s)
368 //, params_ (params)
369 , backend_(backend)
370 , seq2_(2)
371 , gen_(id + 1)
372 , dist_(0, params.items - 1)
373 {
374 }
375
376 void
377 operator()(std::size_t i)
378 {
379 try
380 {
381 auto const hash = seq2_.key(i);
383 backend_.fetch(hash, &result);
384 suite_.expect(!result);
385 }
386 catch (std::exception const& e)
387 {
388 suite_.fail(e.what());
389 }
390 }
391 };
392
393 try
394 {
395 parallel_for_id<Body>(
396 params.items,
397 params.threads,
398 std::ref(*this),
399 std::ref(params),
400 std::ref(*backend));
401 }
402 catch (std::exception const&)
403 {
404#if NODESTORE_TIMING_DO_VERIFY
405 backend->verify();
406#endif
407 Rethrow();
408 }
409 backend->close();
410 }
411
412 // Fetch with present and missing keys
413 void
414 do_mixed(Section const& config, Params const& params, beast::Journal journal)
415 {
416 DummyScheduler scheduler;
417 auto backend = make_Backend(config, scheduler, journal);
418 BEAST_EXPECT(backend != nullptr);
419 backend->open();
420
421 class Body
422 {
423 private:
424 suite& suite_;
425 // Params const& params_;
426 Backend& backend_;
427 Sequence seq1_;
428 Sequence seq2_;
432
433 public:
434 Body(std::size_t id, suite& s, Params const& params, Backend& backend)
435 : suite_(s)
436 //, params_ (params)
437 , backend_(backend)
438 , seq1_(1)
439 , seq2_(2)
440 , gen_(id + 1)
441 , rand_(0, 99)
442 , dist_(0, params.items - 1)
443 {
444 }
445
446 void
447 operator()(std::size_t i)
448 {
449 try
450 {
451 if (rand_(gen_) < missingNodePercent)
452 {
453 auto const hash = seq2_.key(dist_(gen_));
455 backend_.fetch(hash, &result);
456 suite_.expect(!result);
457 }
458 else
459 {
462 obj = seq1_.obj(dist_(gen_));
463 backend_.fetch(obj->getHash(), &result);
464 suite_.expect(result && isSame(result, obj));
465 }
466 }
467 catch (std::exception const& e)
468 {
469 suite_.fail(e.what());
470 }
471 }
472 };
473
474 try
475 {
476 parallel_for_id<Body>(
477 params.items,
478 params.threads,
479 std::ref(*this),
480 std::ref(params),
481 std::ref(*backend));
482 }
483 catch (std::exception const&)
484 {
485#if NODESTORE_TIMING_DO_VERIFY
486 backend->verify();
487#endif
488 Rethrow();
489 }
490 backend->close();
491 }
492
493 // Simulate a rippled workload:
494 // Each thread randomly:
495 // inserts a new key
496 // fetches an old key
497 // fetches recent, possibly non existent data
498 void
499 do_work(Section const& config, Params const& params, beast::Journal journal)
500 {
501 DummyScheduler scheduler;
502 auto backend = make_Backend(config, scheduler, journal);
503 BEAST_EXPECT(backend != nullptr);
504 backend->setDeletePath();
505 backend->open();
506
507 class Body
508 {
509 private:
510 suite& suite_;
511 Params const& params_;
512 Backend& backend_;
513 Sequence seq1_;
518
519 public:
520 Body(std::size_t id, suite& s, Params const& params, Backend& backend)
521 : suite_(s)
522 , params_(params)
523 , backend_(backend)
524 , seq1_(1)
525 , gen_(id + 1)
526 , rand_(0, 99)
527 , recent_(params.items, (params.items * 2) - 1)
528 , older_(0, params.items - 1)
529 {
530 }
531
532 void
533 operator()(std::size_t i)
534 {
535 try
536 {
537 if (rand_(gen_) < 200)
538 {
539 // historical lookup
542 auto const j = older_(gen_);
543 obj = seq1_.obj(j);
544 backend_.fetch(obj->getHash(), &result);
545 suite_.expect(result != nullptr);
546 suite_.expect(isSame(result, obj));
547 }
548
549 char p[2];
550 p[0] = rand_(gen_) < 50 ? 0 : 1;
551 p[1] = 1 - p[0];
552 for (int q = 0; q < 2; ++q)
553 {
554 // NOLINTNEXTLINE(bugprone-switch-missing-default-case)
555 switch (p[q])
556 {
557 case 0: {
558 // fetch recent
561 auto const j = recent_(gen_);
562 obj = seq1_.obj(j);
563 backend_.fetch(obj->getHash(), &result);
564 suite_.expect(!result || isSame(result, obj));
565 break;
566 }
567
568 case 1: {
569 // insert new
570 auto const j = i + params_.items;
571 backend_.store(seq1_.obj(j));
572 break;
573 }
574 }
575 }
576 }
577 catch (std::exception const& e)
578 {
579 suite_.fail(e.what());
580 }
581 }
582 };
583
584 try
585 {
586 parallel_for_id<Body>(
587 params.items,
588 params.threads,
589 std::ref(*this),
590 std::ref(params),
591 std::ref(*backend));
592 }
593 catch (std::exception const&)
594 {
595#if NODESTORE_TIMING_DO_VERIFY
596 backend->verify();
597#endif
598 Rethrow();
599 }
600 backend->close();
601 }
602
603 //--------------------------------------------------------------------------
604
605 using test_func = void (Timing_test::*)(Section const&, Params const&, beast::Journal);
607
609 do_test(test_func f, Section const& config, Params const& params, beast::Journal journal)
610 {
611 auto const start = clock_type::now();
612 (this->*f)(config, params, journal);
613 return std::chrono::duration_cast<duration_type>(clock_type::now() - start);
614 }
615
616 void
618 std::size_t threads,
619 test_list const& tests,
620 std::vector<std::string> const& config_strings)
621 {
622 using std::setw;
623 int w = 8;
624 for (auto const& test : tests)
625 {
626 w = std::max<std::basic_string<char>::size_type>(w, test.first.size());
627 }
628 log << threads << " Thread" << (threads > 1 ? "s" : "") << ", " << default_items
629 << " Objects" << std::endl;
630 {
632 ss << std::left << setw(10) << "Backend" << std::right;
633 for (auto const& test : tests)
634 ss << " " << setw(w) << test.first;
635 log << ss.str() << std::endl;
636 }
637
638 using namespace beast::severities;
639 test::SuiteJournal journal("Timing_test", *this);
640
641 for (auto const& config_string : config_strings)
642 {
643 Params params{};
644 params.items = default_items;
645 params.threads = threads;
646 for (auto i = default_repeat; (i--) != 0u;)
647 {
648 beast::temp_dir const tempDir;
649 Section config = parse(config_string);
650 config.set("path", tempDir.path());
652 ss << std::left << setw(10) << get(config, "type", std::string()) << std::right;
653 for (auto const& test : tests)
654 {
655 ss << " " << setw(w)
656 << to_string(do_test(test.second, config, params, journal));
657 }
658 ss << " " << to_string(config);
659 log << ss.str() << std::endl;
660 }
661 }
662 }
663
664 void
665 run() override
666 {
668
669 /* Parameters:
670
671 repeat Number of times to repeat each test
672 items Number of objects to create in the database
673
674 */
675 std::string const default_args =
676 "type=nudb"
677#if XRPL_ROCKSDB_AVAILABLE
678 ";type=rocksdb,open_files=2000,filter_bits=12,cache_mb=256,"
679 "file_size_mb=8,file_size_mult=2"
680#endif
681#if 0
682 ";type=memory|path=NodeStore"
683#endif
684 ;
685
686 test_list const tests = {
687 {"Insert", &Timing_test::do_insert},
688 {"Fetch", &Timing_test::do_fetch},
689 {"Missing", &Timing_test::do_missing},
690 {"Mixed", &Timing_test::do_mixed},
691 {"Work", &Timing_test::do_work}};
692
693 auto args = arg().empty() ? default_args : arg();
694 std::vector<std::string> config_strings;
695 boost::split(config_strings, args, boost::algorithm::is_any_of(";"));
696 for (auto iter = config_strings.begin(); iter != config_strings.end();)
697 {
698 if (iter->empty())
699 {
700 iter = config_strings.erase(iter);
701 }
702 else
703 {
704 ++iter;
705 }
706 }
707
708 do_tests(1, tests, config_strings);
709 do_tests(4, tests, config_strings);
710 do_tests(8, tests, config_strings);
711 // do_tests (16, tests, config_strings);
712 }
713};
714
715BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(Timing, nodestore, xrpl, 1);
716
717} // namespace NodeStore
718} // namespace xrpl
T begin(T... args)
A generic endpoint for log messages.
Definition Journal.h:40
RAII temporary directory.
Definition temp_dir.h:15
std::string path() const
Get the native path for the temporary directory.
Definition temp_dir.h:47
A testsuite class.
Definition suite.h:51
log_os< char > log
Logging output stream.
Definition suite.h:147
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:150
std::string const & arg() const
Return the argument associated with the runner.
Definition suite.h:279
static std::shared_ptr< NodeObject > createObject(NodeObjectType type, Blob &&data, uint256 const &hash)
Create an object from fields.
A backend used for the NodeStore.
Definition Backend.h:20
virtual void store(std::shared_ptr< NodeObject > const &object)=0
Store a single object.
virtual Status fetch(uint256 const &hash, std::shared_ptr< NodeObject > *pObject)=0
Fetch a single object.
Simple NodeStore Scheduler that just performs the tasks synchronously.
static Manager & instance()
Returns the instance of the manager singleton.
virtual std::unique_ptr< Backend > make_Backend(Section const &parameters, std::size_t burstSize, Scheduler &scheduler, beast::Journal journal)=0
Create a backend.
Scheduling for asynchronous backend activity.
std::uniform_int_distribution< std::uint32_t > d_size_
beast::xor_shift_engine gen_
Sequence(std::uint8_t prefix)
std::discrete_distribution< std::uint32_t > d_type_
std::shared_ptr< NodeObject > obj(std::size_t n)
void batch(std::size_t n, Batch &b, std::size_t size)
uint256 key(std::size_t n)
parallel_for_lambda(std::size_t n, std::atomic< std::size_t > &c)
void(Timing_test::*)(Section const &, Params const &, beast::Journal) test_func
static std::string to_string(duration_type const &d)
void do_work(Section const &config, Params const &params, beast::Journal journal)
duration_type do_test(test_func f, Section const &config, Params const &params, beast::Journal journal)
void do_missing(Section const &config, Params const &params, beast::Journal journal)
std::size_t const default_items
void parallel_for(std::size_t const n, std::size_t number_of_threads, Args const &... args)
void do_insert(Section const &config, Params const &params, beast::Journal journal)
static Section parse(std::string s)
void parallel_for_id(std::size_t const n, std::size_t number_of_threads, Args const &... args)
void do_tests(std::size_t threads, test_list const &tests, std::vector< std::string > const &config_strings)
static std::string to_string(Section const &config)
void run() override
Runs the suite.
void do_fetch(Section const &config, Params const &params, beast::Journal journal)
void do_mixed(Section const &config, Params const &params, beast::Journal journal)
std::size_t const default_repeat
Holds a collection of configuration values.
Definition BasicConfig.h:24
const_iterator end() const
void set(std::string const &key, std::string const &value)
Set a key/value pair.
const_iterator begin() const
void append(std::vector< std::string > const &lines)
Append a set of lines to this section.
iterator begin()
Definition base_uint.h:112
static constexpr std::size_t size()
Definition base_uint.h:499
T clear(T... args)
T emplace_back(T... args)
T empty(T... args)
T end(T... args)
T endl(T... args)
T erase(T... args)
T fixed(T... args)
T is_same_v
T left(T... args)
A namespace for easy access to logging severity values.
Definition Journal.h:10
bool isSame(std::shared_ptr< NodeObject > const &lhs, std::shared_ptr< NodeObject > const &rhs)
Returns true if objects are identical.
Definition TestBase.h:38
std::unique_ptr< Backend > make_Backend(Section const &config, Scheduler &scheduler, beast::Journal journal)
static void rngcpy(void *buffer, std::size_t bytes, Generator &g)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
XRPL_NO_SANITIZE_ADDRESS void Rethrow()
Rethrow the exception currently being handled.
Definition contract.h:33
constexpr auto megabytes(T value) noexcept
T ref(T... args)
T reserve(T... args)
T setprecision(T... args)
T setw(T... args)
T size(T... args)
T str(T... args)
T what(T... args)