xrpld
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/Blob.h>
5#include <xrpl/basics/ByteUtilities.h>
6#include <xrpl/basics/base_uint.h>
7#include <xrpl/basics/contract.h>
8#include <xrpl/basics/safe_cast.h>
9#include <xrpl/beast/unit_test/suite.h>
10#include <xrpl/beast/unit_test/thread.h>
11#include <xrpl/beast/utility/Journal.h>
12#include <xrpl/beast/utility/temp_dir.h>
13#include <xrpl/beast/xor_shift_engine.h>
14#include <xrpl/config/BasicConfig.h>
15#include <xrpl/config/Constants.h>
16#include <xrpl/nodestore/Backend.h>
17#include <xrpl/nodestore/DummyScheduler.h>
18#include <xrpl/nodestore/Manager.h>
19#include <xrpl/nodestore/NodeObject.h>
20#include <xrpl/nodestore/Scheduler.h>
21#include <xrpl/nodestore/Types.h>
22
23#include <boost/algorithm/string/classification.hpp>
24#include <boost/algorithm/string/split.hpp>
25
26#include <algorithm>
27#include <atomic>
28#include <chrono>
29#include <cstddef>
30#include <cstdint>
31#include <exception>
32#include <functional>
33#include <iomanip>
34#include <ios>
35#include <memory>
36#include <ostream>
37#include <random>
38#include <sstream>
39#include <string>
40#include <utility>
41#include <vector>
42
43#ifndef NODESTORE_TIMING_DO_VERIFY
44#define NODESTORE_TIMING_DO_VERIFY 0
45#endif
46
47namespace xrpl::NodeStore {
48
49std::unique_ptr<Backend>
50makeBackend(Section const& config, Scheduler& scheduler, beast::Journal journal)
51{
52 return Manager::instance().makeBackend(config, megabytes(4), scheduler, journal);
53}
54
55// Fill memory with random bits
56template <class Generator>
57static void
58rngcpy(void* buffer, std::size_t bytes, Generator& g)
59{
60 using result_type = Generator::result_type;
61 while (bytes >= sizeof(result_type))
62 {
63 auto const v = g();
64 memcpy(buffer, &v, sizeof(v));
65 buffer = reinterpret_cast<std::uint8_t*>(buffer) + sizeof(v);
66 bytes -= sizeof(v);
67 }
68
69 if (bytes > 0)
70 {
71 auto const v = g();
72 memcpy(buffer, &v, bytes);
73 }
74}
75
76// Instance of node factory produces a deterministic sequence
77// of random NodeObjects within the given
79{
80private:
81 static constexpr auto kMinLedger = 1;
82 static constexpr auto kMaxLedger = 1000000;
83 static constexpr auto kMinSize = 250;
84 static constexpr auto kMaxSize = 1250;
85
90
91public:
92 explicit Sequence(std::uint8_t prefix)
93 : prefix_(prefix)
94 // uniform distribution over hotLEDGER - hotTRANSACTION_NODE
95 // but exclude hotTRANSACTION = 2 (removed)
96 , dType_({1, 1, 0, 1, 1})
98 {
99 }
100
101 // Returns the n-th key
102 uint256
104 {
105 gen_.seed(n + 1);
106 uint256 result;
107 rngcpy(&*result.begin(), result.size(), gen_);
108 return result;
109 }
110
111 // Returns the n-th complete NodeObject
114 {
115 gen_.seed(n + 1);
116 uint256 key;
117 auto const data = static_cast<std::uint8_t*>(&*key.begin());
118 *data = prefix_;
119 rngcpy(data + 1, key.size() - 1, gen_);
120 Blob value(dSize_(gen_));
121 rngcpy(&value[0], value.size(), gen_);
123 safeCast<NodeObjectType>(dType_(gen_)), std::move(value), key);
124 }
125
126 // returns a batch of NodeObjects starting at n
127 void
129 {
130 b.clear();
131 b.reserve(size);
132 while ((size--) != 0u)
133 b.emplace_back(obj(n++));
134 }
135};
136
137//----------------------------------------------------------------------------------
138
140{
141public:
142 static constexpr auto kMissingNodePercent = 20; // percent of fetches for missing nodes
143
145#ifndef NDEBUG
147#else
148 std::size_t const defaultItems = 100000; // release
149#endif
150
153
159
160 static std::string
161 toString(Section const& config)
162 {
163 std::string s;
164 for (auto iter = config.begin(); iter != config.end(); ++iter)
165 s += (iter != config.begin() ? "," : "") + iter->first + "=" + iter->second;
166 return s;
167 }
168
169 static std::string
171 {
173 ss << std::fixed << std::setprecision(3) << (d.count() / 1000.) << "s";
174 return ss.str();
175 }
176
177 static Section
179 {
180 Section section;
182 boost::split(v, s, boost::algorithm::is_any_of(","));
183 section.append(v);
184 return section;
185 }
186
187 //--------------------------------------------------------------------------
188
189 // Workaround for GCC's parameter pack expansion in lambdas
190 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47226
191 template <class Body>
193 {
194 private:
197
198 public:
202
203 template <class... Args>
204 void
205 operator()(Args&&... args)
206 {
207 Body body(args...);
208 for (;;)
209 {
210 auto const i = c_++;
211 if (i >= n_)
212 break;
213 body(i);
214 }
215 }
216 };
217
218 /* Execute parallel-for loop.
219
220 Constructs `number_of_threads` instances of `Body`
221 with `args...` parameters and runs them on individual threads
222 with unique loop indexes in the range [0, n).
223 */
224 template <class Body, class... Args>
225 void
226 parallelFor(std::size_t const n, std::size_t numberOfThreads, Args const&... args)
227 {
230 t.reserve(numberOfThreads);
231 for (std::size_t id = 0; id < numberOfThreads; ++id)
232 t.emplace_back(*this, ParallelForLambda<Body>(n, c), args...);
233 for (auto& _ : t)
234 _.join();
235 }
236
237 template <class Body, class... Args>
238 void
239 parallelForId(std::size_t const n, std::size_t numberOfThreads, Args const&... args)
240 {
243 t.reserve(numberOfThreads);
244 for (std::size_t id = 0; id < numberOfThreads; ++id)
245 t.emplace_back(*this, ParallelForLambda<Body>(n, c), id, args...);
246 for (auto& _ : t)
247 _.join();
248 }
249
250 //--------------------------------------------------------------------------
251
252 // Insert only
253 void
254 doInsert(Section const& config, Params const& params, beast::Journal journal)
255 {
256 DummyScheduler scheduler;
257 auto backend = makeBackend(config, scheduler, journal);
258 BEAST_EXPECT(backend != nullptr);
259 backend->open();
260
261 class Body
262 {
263 private:
264 Suite& suite_;
265 Backend& backend_;
266 Sequence seq_;
267
268 public:
269 explicit Body(Suite& s, Backend& backend) : suite_(s), backend_(backend), seq_(1)
270 {
271 }
272
273 void
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 parallelFor<Body>(params.items, params.threads, std::ref(*this), std::ref(*backend));
290 }
291 catch (std::exception const&)
292 {
293#if NODESTORE_TIMING_DO_VERIFY
294 backend->verify();
295#endif
296 rethrow();
297 }
298 backend->close();
299 }
300
301 // Fetch existing keys
302 void
303 doFetch(Section const& config, Params const& params, beast::Journal journal)
304 {
305 DummyScheduler scheduler;
306 auto backend = makeBackend(config, scheduler, journal);
307 BEAST_EXPECT(backend != nullptr);
308 backend->open();
309
310 class Body
311 {
312 private:
313 Suite& suite_;
314 Backend& backend_;
315 Sequence seq1_;
318
319 public:
320 Body(std::size_t id, Suite& s, Params const& params, Backend& backend)
321 : suite_(s), backend_(backend), seq1_(1), gen_(id + 1), dist_(0, params.items - 1)
322 {
323 }
324
325 void
327 {
328 try
329 {
332 obj = seq1_.obj(dist_(gen_));
333 backend_.fetch(obj->getHash(), &result);
334 suite_.expect(result && isSame(result, obj));
335 }
336 catch (std::exception const& e)
337 {
338 suite_.fail(e.what());
339 }
340 }
341 };
342 try
343 {
345 params.items,
346 params.threads,
347 std::ref(*this),
348 std::ref(params),
349 std::ref(*backend));
350 }
351 catch (std::exception const&)
352 {
353#if NODESTORE_TIMING_DO_VERIFY
354 backend->verify();
355#endif
356 rethrow();
357 }
358 backend->close();
359 }
360
361 // Perform lookups of non-existent keys
362 void
363 doMissing(Section const& config, Params const& params, beast::Journal journal)
364 {
365 DummyScheduler scheduler;
366 auto backend = makeBackend(config, scheduler, journal);
367 BEAST_EXPECT(backend != nullptr);
368 backend->open();
369
370 class Body
371 {
372 private:
373 Suite& suite_;
374 // Params const& params_;
375 Backend& backend_;
376 Sequence seq2_;
379
380 public:
381 Body(std::size_t id, Suite& s, Params const& params, Backend& backend)
382 : suite_(s)
383 //, params_ (params)
384 , backend_(backend)
385 , seq2_(2)
386 , gen_(id + 1)
387 , dist_(0, params.items - 1)
388 {
389 }
390
391 void
393 {
394 try
395 {
396 auto const hash = seq2_.key(i);
398 backend_.fetch(hash, &result);
399 suite_.expect(!result);
400 }
401 catch (std::exception const& e)
402 {
403 suite_.fail(e.what());
404 }
405 }
406 };
407
408 try
409 {
411 params.items,
412 params.threads,
413 std::ref(*this),
414 std::ref(params),
415 std::ref(*backend));
416 }
417 catch (std::exception const&)
418 {
419#if NODESTORE_TIMING_DO_VERIFY
420 backend->verify();
421#endif
422 rethrow();
423 }
424 backend->close();
425 }
426
427 // Fetch with present and missing keys
428 void
429 doMixed(Section const& config, Params const& params, beast::Journal journal)
430 {
431 DummyScheduler scheduler;
432 auto backend = makeBackend(config, scheduler, journal);
433 BEAST_EXPECT(backend != nullptr);
434 backend->open();
435
436 class Body
437 {
438 private:
439 Suite& suite_;
440 // Params const& params_;
441 Backend& backend_;
442 Sequence seq1_;
443 Sequence seq2_;
447
448 public:
449 Body(std::size_t id, Suite& s, Params const& params, Backend& backend)
450 : suite_(s)
451 //, params_ (params)
452 , backend_(backend)
453 , seq1_(1)
454 , seq2_(2)
455 , gen_(id + 1)
456 , rand_(0, 99)
457 , dist_(0, params.items - 1)
458 {
459 }
460
461 void
463 {
464 try
465 {
466 if (rand_(gen_) < kMissingNodePercent)
467 {
468 auto const hash = seq2_.key(dist_(gen_));
470 backend_.fetch(hash, &result);
471 suite_.expect(!result);
472 }
473 else
474 {
477 obj = seq1_.obj(dist_(gen_));
478 backend_.fetch(obj->getHash(), &result);
479 suite_.expect(result && isSame(result, obj));
480 }
481 }
482 catch (std::exception const& e)
483 {
484 suite_.fail(e.what());
485 }
486 }
487 };
488
489 try
490 {
492 params.items,
493 params.threads,
494 std::ref(*this),
495 std::ref(params),
496 std::ref(*backend));
497 }
498 catch (std::exception const&)
499 {
500#if NODESTORE_TIMING_DO_VERIFY
501 backend->verify();
502#endif
503 rethrow();
504 }
505 backend->close();
506 }
507
508 // Simulate an xrpld workload:
509 // Each thread randomly:
510 // inserts a new key
511 // fetches an old key
512 // fetches recent, possibly non existent data
513 void
514 doWork(Section const& config, Params const& params, beast::Journal journal)
515 {
516 DummyScheduler scheduler;
517 auto backend = makeBackend(config, scheduler, journal);
518 BEAST_EXPECT(backend != nullptr);
519 backend->setDeletePath();
520 backend->open();
521
522 class Body
523 {
524 private:
525 Suite& suite_;
526 Params const& params_;
527 Backend& backend_;
528 Sequence seq1_;
533
534 public:
535 Body(std::size_t id, Suite& s, Params const& params, Backend& backend)
536 : suite_(s)
537 , params_(params)
538 , backend_(backend)
539 , seq1_(1)
540 , gen_(id + 1)
541 , rand_(0, 99)
542 , recent_(params.items, (params.items * 2) - 1)
543 , older_(0, params.items - 1)
544 {
545 }
546
547 void
549 {
550 try
551 {
552 if (rand_(gen_) < 200)
553 {
554 // historical lookup
557 auto const j = older_(gen_);
558 obj = seq1_.obj(j);
559 backend_.fetch(obj->getHash(), &result);
560 suite_.expect(result != nullptr);
561 suite_.expect(isSame(result, obj));
562 }
563
564 char p[2];
565 p[0] = rand_(gen_) < 50 ? 0 : 1;
566 p[1] = 1 - p[0];
567 for (int q = 0; q < 2; ++q)
568 {
569 // NOLINTNEXTLINE(bugprone-switch-missing-default-case)
570 switch (p[q])
571 {
572 case 0: {
573 // fetch recent
576 auto const j = recent_(gen_);
577 obj = seq1_.obj(j);
578 backend_.fetch(obj->getHash(), &result);
579 suite_.expect(!result || isSame(result, obj));
580 break;
581 }
582
583 case 1: {
584 // insert new
585 auto const j = i + params_.items;
586 backend_.store(seq1_.obj(j));
587 break;
588 }
589 }
590 }
591 }
592 catch (std::exception const& e)
593 {
594 suite_.fail(e.what());
595 }
596 }
597 };
598
599 try
600 {
602 params.items,
603 params.threads,
604 std::ref(*this),
605 std::ref(params),
606 std::ref(*backend));
607 }
608 catch (std::exception const&)
609 {
610#if NODESTORE_TIMING_DO_VERIFY
611 backend->verify();
612#endif
613 rethrow();
614 }
615 backend->close();
616 }
617
618 //--------------------------------------------------------------------------
619
620 using test_func = void (Timing_test::*)(Section const&, Params const&, beast::Journal);
622
624 doTest(test_func f, Section const& config, Params const& params, beast::Journal journal)
625 {
626 auto const start = clock_type::now();
627 (this->*f)(config, params, journal);
629 }
630
631 void
633 std::size_t threads,
634 test_list const& tests,
635 std::vector<std::string> const& configStrings)
636 {
637 using std::setw;
638 int w = 8;
639 for (auto const& test : tests)
640 {
642 }
643 log << threads << " Thread" << (threads > 1 ? "s" : "") << ", " << defaultItems
644 << " Objects" << std::endl;
645 {
647 ss << std::left << setw(10) << "Backend" << std::right;
648 for (auto const& test : tests)
649 ss << " " << setw(w) << test.first;
650 log << ss.str() << std::endl;
651 }
652
653 using beast::Severity;
654 test::SuiteJournal journal("Timing_test", *this);
655
656 for (auto const& configString : configStrings)
657 {
658 Params params{};
659 params.items = defaultItems;
660 params.threads = threads;
661 for (auto i = defaultRepeat; (i--) != 0u;)
662 {
663 beast::TempDir const tempDir;
664 Section config = parse(configString);
665 config.set(Keys::kPath, tempDir.path());
667 ss << std::left << setw(10) << get(config, Keys::kType, std::string())
668 << std::right;
669 for (auto const& test : tests)
670 {
671 ss << " " << setw(w) << toString(doTest(test.second, config, params, journal));
672 }
673 ss << " " << toString(config);
674 log << ss.str() << std::endl;
675 }
676 }
677 }
678
679 void
680 run() override
681 {
683
684 /* Parameters:
685
686 repeat Number of times to repeat each test
687 items Number of objects to create in the database
688
689 */
690 std::string const defaultArgs =
691 "type=nudb"
692#if XRPL_ROCKSDB_AVAILABLE
693 ";type=rocksdb,open_files=2000,filter_bits=12,cache_mb=256,"
694 "file_size_mb=8,file_size_mult=2"
695#endif
696#if 0
697 ";type=memory|path=NodeStore"
698#endif
699 ;
700
701 test_list const tests = {
702 {"Insert", &Timing_test::doInsert},
703 {"Fetch", &Timing_test::doFetch},
704 {"Missing", &Timing_test::doMissing},
705 {"Mixed", &Timing_test::doMixed},
706 {"Work", &Timing_test::doWork}};
707
708 auto args = arg().empty() ? defaultArgs : arg();
709 std::vector<std::string> configStrings;
710 boost::split(configStrings, args, boost::algorithm::is_any_of(";"));
711 for (auto iter = configStrings.begin(); iter != configStrings.end();)
712 {
713 if (iter->empty())
714 {
715 iter = configStrings.erase(iter);
716 }
717 else
718 {
719 ++iter;
720 }
721 }
722
723 doTests(1, tests, configStrings);
724 doTests(4, tests, configStrings);
725 doTests(8, tests, configStrings);
726 // do_tests (16, tests, config_strings);
727 }
728};
729
731
732} // namespace xrpl::NodeStore
T begin(T... args)
A generic endpoint for log messages.
Definition Journal.h:38
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:50
void operator()(Runner &r)
Invokes the test using the specified runner.
Definition suite.h:388
LogOs< char > log
Logging output stream.
Definition suite.h:146
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:149
std::string const & arg() const
Return the argument associated with the runner.
Definition suite.h:278
iterator begin()
Definition base_uint.h:117
static constexpr std::size_t size()
Definition base_uint.h:530
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:19
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.
virtual std::unique_ptr< Backend > makeBackend(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.
beast::xor_shift_engine gen_
static constexpr auto kMaxLedger
Sequence(std::uint8_t prefix)
static constexpr auto kMinLedger
std::uniform_int_distribution< std::uint32_t > dSize_
static constexpr auto kMaxSize
std::discrete_distribution< std::uint32_t > dType_
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)
static constexpr auto kMinSize
ParallelForLambda(std::size_t n, std::atomic< std::size_t > &c)
void doMixed(Section const &config, Params const &params, beast::Journal journal)
void(Timing_test::*)(Section const &, Params const &, beast::Journal) test_func
void doInsert(Section const &config, Params const &params, beast::Journal journal)
std::chrono::steady_clock clock_type
static std::string toString(duration_type const &d)
void doFetch(Section const &config, Params const &params, beast::Journal journal)
void doWork(Section const &config, Params const &params, beast::Journal journal)
void doMissing(Section const &config, Params const &params, beast::Journal journal)
std::chrono::milliseconds duration_type
std::vector< std::pair< std::string, test_func > > test_list
void parallelFor(std::size_t const n, std::size_t numberOfThreads, Args const &... args)
static Section parse(std::string s)
std::size_t const defaultItems
static std::string toString(Section const &config)
duration_type doTest(test_func f, Section const &config, Params const &params, beast::Journal journal)
void doTests(std::size_t threads, test_list const &tests, std::vector< std::string > const &configStrings)
void parallelForId(std::size_t const n, std::size_t numberOfThreads, Args const &... args)
std::size_t const defaultRepeat
void run() override
Runs the suite.
static constexpr auto kMissingNodePercent
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.
T clear(T... args)
T duration_cast(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)
T max(T... args)
detail::XorShiftEngine<> xor_shift_engine
XOR-shift Generator.
Severity
Severity level / threshold of a Journal message.
Definition Journal.h:11
bool isSame(std::shared_ptr< NodeObject > const &lhs, std::shared_ptr< NodeObject > const &rhs)
Returns true if objects are identical.
Definition TestBase.h:37
BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(Timing, nodestore, xrpl, 1)
static void rngcpy(void *buffer, std::size_t bytes, Generator &g)
std::unique_ptr< Backend > makeBackend(Section const &config, Scheduler &scheduler, beast::Journal journal)
std::vector< std::shared_ptr< NodeObject > > Batch
A batch of NodeObjects to write at once.
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.
constexpr std::enable_if_t< std::is_integral_v< Dest > &&std::is_integral_v< Src >, Dest > safeCast(Src s) noexcept
Definition safe_cast.h:21
XRPL_NO_SANITIZE_ADDRESS void rethrow()
Rethrow the exception currently being handled.
Definition contract.h:33
constexpr auto megabytes(T value) noexcept
std::vector< unsigned char > Blob
Storage for linear binary data.
Definition Blob.h:10
BaseUInt< 256 > uint256
Definition base_uint.h:562
T ref(T... args)
T reserve(T... args)
T setprecision(T... args)
T setw(T... args)
T str(T... args)
static constexpr auto kType
Definition Constants.h:170
static constexpr auto kPath
Definition Constants.h:140
T what(T... args)