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