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 xrpl {
31namespace NodeStore {
32
34make_Backend(Section const& config, Scheduler& scheduler, beast::Journal journal)
35{
36 return Manager::instance().make_Backend(config, megabytes(4), scheduler, journal);
37}
38
39// Fill memory with random bits
40template <class Generator>
41static void
42rngcpy(void* buffer, std::size_t bytes, Generator& g)
43{
44 using result_type = typename Generator::result_type;
45 while (bytes >= sizeof(result_type))
46 {
47 auto const v = g();
48 memcpy(buffer, &v, sizeof(v));
49 buffer = reinterpret_cast<std::uint8_t*>(buffer) + sizeof(v);
50 bytes -= sizeof(v);
51 }
52
53 if (bytes > 0)
54 {
55 auto const v = g();
56 memcpy(buffer, &v, bytes);
57 }
58}
59
60// Instance of node factory produces a deterministic sequence
61// of random NodeObjects within the given
63{
64private:
65 enum { minLedger = 1, maxLedger = 1000000, minSize = 250, maxSize = 1250 };
66
71
72public:
73 explicit Sequence(std::uint8_t prefix)
74 : prefix_(prefix)
75 // uniform distribution over hotLEDGER - hotTRANSACTION_NODE
76 // but exclude hotTRANSACTION = 2 (removed)
77 , d_type_({1, 1, 0, 1, 1})
79 {
80 }
81
82 // Returns the n-th key
85 {
86 gen_.seed(n + 1);
87 uint256 result;
88 rngcpy(&*result.begin(), result.size(), gen_);
89 return result;
90 }
91
92 // Returns the n-th complete NodeObject
95 {
96 gen_.seed(n + 1);
98 auto const data = static_cast<std::uint8_t*>(&*key.begin());
99 *data = prefix_;
100 rngcpy(data + 1, key.size() - 1, gen_);
101 Blob value(d_size_(gen_));
102 rngcpy(&value[0], value.size(), gen_);
103 return NodeObject::createObject(safe_cast<NodeObjectType>(d_type_(gen_)), std::move(value), key);
104 }
105
106 // returns a batch of NodeObjects starting at n
107 void
109 {
110 b.clear();
111 b.reserve(size);
112 while (size--)
113 b.emplace_back(obj(n++));
114 }
115};
116
117//----------------------------------------------------------------------------------
118
120{
121public:
122 enum {
123 // percent of fetches for missing nodes
125 };
126
128#ifndef NDEBUG
130#else
131 std::size_t const default_items = 100000; // release
132#endif
133
136
142
143 static std::string
144 to_string(Section const& config)
145 {
146 std::string s;
147 for (auto iter = config.begin(); iter != config.end(); ++iter)
148 s += (iter != config.begin() ? "," : "") + iter->first + "=" + iter->second;
149 return s;
150 }
151
152 static std::string
154 {
156 ss << std::fixed << std::setprecision(3) << (d.count() / 1000.) << "s";
157 return ss.str();
158 }
159
160 static Section
162 {
163 Section section;
165 boost::split(v, s, boost::algorithm::is_any_of(","));
166 section.append(v);
167 return section;
168 }
169
170 //--------------------------------------------------------------------------
171
172 // Workaround for GCC's parameter pack expansion in lambdas
173 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47226
174 template <class Body>
176 {
177 private:
180
181 public:
185
186 template <class... Args>
187 void
188 operator()(Args&&... args)
189 {
190 Body body(args...);
191 for (;;)
192 {
193 auto const i = c_++;
194 if (i >= n_)
195 break;
196 body(i);
197 }
198 }
199 };
200
201 /* Execute parallel-for loop.
202
203 Constructs `number_of_threads` instances of `Body`
204 with `args...` parameters and runs them on individual threads
205 with unique loop indexes in the range [0, n).
206 */
207 template <class Body, class... Args>
208 void
209 parallel_for(std::size_t const n, std::size_t number_of_threads, Args const&... args)
210 {
213 t.reserve(number_of_threads);
214 for (std::size_t id = 0; id < number_of_threads; ++id)
215 t.emplace_back(*this, parallel_for_lambda<Body>(n, c), args...);
216 for (auto& _ : t)
217 _.join();
218 }
219
220 template <class Body, class... Args>
221 void
222 parallel_for_id(std::size_t const n, std::size_t number_of_threads, Args const&... args)
223 {
226 t.reserve(number_of_threads);
227 for (std::size_t id = 0; id < number_of_threads; ++id)
228 t.emplace_back(*this, parallel_for_lambda<Body>(n, c), id, args...);
229 for (auto& _ : t)
230 _.join();
231 }
232
233 //--------------------------------------------------------------------------
234
235 // Insert only
236 void
237 do_insert(Section const& config, Params const& params, beast::Journal journal)
238 {
239 DummyScheduler scheduler;
240 auto backend = make_Backend(config, scheduler, journal);
241 BEAST_EXPECT(backend != nullptr);
242 backend->open();
243
244 class Body
245 {
246 private:
247 suite& suite_;
248 Backend& backend_;
249 Sequence seq_;
250
251 public:
252 explicit Body(suite& s, Backend& backend) : suite_(s), backend_(backend), seq_(1)
253 {
254 }
255
256 void
257 operator()(std::size_t i)
258 {
259 try
260 {
261 backend_.store(seq_.obj(i));
262 }
263 catch (std::exception const& e)
264 {
265 suite_.fail(e.what());
266 }
267 }
268 };
269
270 try
271 {
272 parallel_for<Body>(params.items, params.threads, std::ref(*this), std::ref(*backend));
273 }
274 catch (std::exception const&)
275 {
276#if NODESTORE_TIMING_DO_VERIFY
277 backend->verify();
278#endif
279 Rethrow();
280 }
281 backend->close();
282 }
283
284 // Fetch existing keys
285 void
286 do_fetch(Section const& config, Params const& params, beast::Journal journal)
287 {
288 DummyScheduler scheduler;
289 auto backend = make_Backend(config, scheduler, journal);
290 BEAST_EXPECT(backend != nullptr);
291 backend->open();
292
293 class Body
294 {
295 private:
296 suite& suite_;
297 Backend& backend_;
298 Sequence seq1_;
301
302 public:
303 Body(std::size_t id, suite& s, Params const& params, Backend& backend)
304 : suite_(s), backend_(backend), seq1_(1), gen_(id + 1), dist_(0, params.items - 1)
305 {
306 }
307
308 void
309 operator()(std::size_t i)
310 {
311 try
312 {
315 obj = seq1_.obj(dist_(gen_));
316 backend_.fetch(obj->getHash().data(), &result);
317 suite_.expect(result && isSame(result, obj));
318 }
319 catch (std::exception const& e)
320 {
321 suite_.fail(e.what());
322 }
323 }
324 };
325 try
326 {
327 parallel_for_id<Body>(params.items, params.threads, std::ref(*this), std::ref(params), std::ref(*backend));
328 }
329 catch (std::exception const&)
330 {
331#if NODESTORE_TIMING_DO_VERIFY
332 backend->verify();
333#endif
334 Rethrow();
335 }
336 backend->close();
337 }
338
339 // Perform lookups of non-existent keys
340 void
341 do_missing(Section const& config, Params const& params, beast::Journal journal)
342 {
343 DummyScheduler scheduler;
344 auto backend = make_Backend(config, scheduler, journal);
345 BEAST_EXPECT(backend != nullptr);
346 backend->open();
347
348 class Body
349 {
350 private:
351 suite& suite_;
352 // Params const& params_;
353 Backend& backend_;
354 Sequence seq2_;
357
358 public:
359 Body(std::size_t id, suite& s, Params const& params, Backend& backend)
360 : suite_(s)
361 //, params_ (params)
362 , backend_(backend)
363 , seq2_(2)
364 , gen_(id + 1)
365 , dist_(0, params.items - 1)
366 {
367 }
368
369 void
370 operator()(std::size_t i)
371 {
372 try
373 {
374 auto const key = seq2_.key(i);
376 backend_.fetch(key.data(), &result);
377 suite_.expect(!result);
378 }
379 catch (std::exception const& e)
380 {
381 suite_.fail(e.what());
382 }
383 }
384 };
385
386 try
387 {
388 parallel_for_id<Body>(params.items, params.threads, std::ref(*this), std::ref(params), std::ref(*backend));
389 }
390 catch (std::exception const&)
391 {
392#if NODESTORE_TIMING_DO_VERIFY
393 backend->verify();
394#endif
395 Rethrow();
396 }
397 backend->close();
398 }
399
400 // Fetch with present and missing keys
401 void
402 do_mixed(Section const& config, Params const& params, beast::Journal journal)
403 {
404 DummyScheduler scheduler;
405 auto backend = make_Backend(config, scheduler, journal);
406 BEAST_EXPECT(backend != nullptr);
407 backend->open();
408
409 class Body
410 {
411 private:
412 suite& suite_;
413 // Params const& params_;
414 Backend& backend_;
415 Sequence seq1_;
416 Sequence seq2_;
420
421 public:
422 Body(std::size_t id, suite& s, Params const& params, Backend& backend)
423 : suite_(s)
424 //, params_ (params)
425 , backend_(backend)
426 , seq1_(1)
427 , seq2_(2)
428 , gen_(id + 1)
429 , rand_(0, 99)
430 , dist_(0, params.items - 1)
431 {
432 }
433
434 void
435 operator()(std::size_t i)
436 {
437 try
438 {
439 if (rand_(gen_) < missingNodePercent)
440 {
441 auto const key = seq2_.key(dist_(gen_));
443 backend_.fetch(key.data(), &result);
444 suite_.expect(!result);
445 }
446 else
447 {
450 obj = seq1_.obj(dist_(gen_));
451 backend_.fetch(obj->getHash().data(), &result);
452 suite_.expect(result && isSame(result, obj));
453 }
454 }
455 catch (std::exception const& e)
456 {
457 suite_.fail(e.what());
458 }
459 }
460 };
461
462 try
463 {
464 parallel_for_id<Body>(params.items, params.threads, std::ref(*this), std::ref(params), std::ref(*backend));
465 }
466 catch (std::exception const&)
467 {
468#if NODESTORE_TIMING_DO_VERIFY
469 backend->verify();
470#endif
471 Rethrow();
472 }
473 backend->close();
474 }
475
476 // Simulate a rippled workload:
477 // Each thread randomly:
478 // inserts a new key
479 // fetches an old key
480 // fetches recent, possibly non existent data
481 void
482 do_work(Section const& config, Params const& params, beast::Journal journal)
483 {
484 DummyScheduler scheduler;
485 auto backend = make_Backend(config, scheduler, journal);
486 BEAST_EXPECT(backend != nullptr);
487 backend->setDeletePath();
488 backend->open();
489
490 class Body
491 {
492 private:
493 suite& suite_;
494 Params const& params_;
495 Backend& backend_;
496 Sequence seq1_;
501
502 public:
503 Body(std::size_t id, suite& s, Params const& params, Backend& backend)
504 : suite_(s)
505 , params_(params)
506 , backend_(backend)
507 , seq1_(1)
508 , gen_(id + 1)
509 , rand_(0, 99)
510 , recent_(params.items, params.items * 2 - 1)
511 , older_(0, params.items - 1)
512 {
513 }
514
515 void
516 operator()(std::size_t i)
517 {
518 try
519 {
520 if (rand_(gen_) < 200)
521 {
522 // historical lookup
525 auto const j = older_(gen_);
526 obj = seq1_.obj(j);
528 backend_.fetch(obj->getHash().data(), &result);
529 suite_.expect(result != nullptr);
530 suite_.expect(isSame(result, obj));
531 }
532
533 char p[2];
534 p[0] = rand_(gen_) < 50 ? 0 : 1;
535 p[1] = 1 - p[0];
536 for (int q = 0; q < 2; ++q)
537 {
538 switch (p[q])
539 {
540 case 0: {
541 // fetch recent
544 auto const j = recent_(gen_);
545 obj = seq1_.obj(j);
546 backend_.fetch(obj->getHash().data(), &result);
547 suite_.expect(!result || isSame(result, obj));
548 break;
549 }
550
551 case 1: {
552 // insert new
553 auto const j = i + params_.items;
554 backend_.store(seq1_.obj(j));
555 break;
556 }
557 }
558 }
559 }
560 catch (std::exception const& e)
561 {
562 suite_.fail(e.what());
563 }
564 }
565 };
566
567 try
568 {
569 parallel_for_id<Body>(params.items, params.threads, std::ref(*this), std::ref(params), std::ref(*backend));
570 }
571 catch (std::exception const&)
572 {
573#if NODESTORE_TIMING_DO_VERIFY
574 backend->verify();
575#endif
576 Rethrow();
577 }
578 backend->close();
579 }
580
581 //--------------------------------------------------------------------------
582
583 using test_func = void (Timing_test::*)(Section const&, Params const&, beast::Journal);
585
587 do_test(test_func f, Section const& config, Params const& params, beast::Journal journal)
588 {
589 auto const start = clock_type::now();
590 (this->*f)(config, params, journal);
591 return std::chrono::duration_cast<duration_type>(clock_type::now() - start);
592 }
593
594 void
595 do_tests(std::size_t threads, test_list const& tests, std::vector<std::string> const& config_strings)
596 {
597 using std::setw;
598 int w = 8;
599 for (auto const& test : tests)
600 if (w < test.first.size())
601 w = test.first.size();
602 log << threads << " Thread" << (threads > 1 ? "s" : "") << ", " << default_items << " Objects" << std::endl;
603 {
605 ss << std::left << setw(10) << "Backend" << std::right;
606 for (auto const& test : tests)
607 ss << " " << setw(w) << test.first;
608 log << ss.str() << std::endl;
609 }
610
611 using namespace beast::severities;
612 test::SuiteJournal journal("Timing_test", *this);
613
614 for (auto const& config_string : config_strings)
615 {
616 Params params;
617 params.items = default_items;
618 params.threads = threads;
619 for (auto i = default_repeat; i--;)
620 {
621 beast::temp_dir tempDir;
622 Section config = parse(config_string);
623 config.set("path", tempDir.path());
625 ss << std::left << setw(10) << get(config, "type", std::string()) << std::right;
626 for (auto const& test : tests)
627 ss << " " << setw(w) << to_string(do_test(test.second, config, params, journal));
628 ss << " " << to_string(config);
629 log << ss.str() << std::endl;
630 }
631 }
632 }
633
634 void
635 run() override
636 {
638
639 /* Parameters:
640
641 repeat Number of times to repeat each test
642 items Number of objects to create in the database
643
644 */
645 std::string default_args =
646 "type=nudb"
647#if XRPL_ROCKSDB_AVAILABLE
648 ";type=rocksdb,open_files=2000,filter_bits=12,cache_mb=256,"
649 "file_size_mb=8,file_size_mult=2"
650#endif
651#if 0
652 ";type=memory|path=NodeStore"
653#endif
654 ;
655
656 test_list const tests = {
657 {"Insert", &Timing_test::do_insert},
658 {"Fetch", &Timing_test::do_fetch},
659 {"Missing", &Timing_test::do_missing},
660 {"Mixed", &Timing_test::do_mixed},
661 {"Work", &Timing_test::do_work}};
662
663 auto args = arg().empty() ? default_args : arg();
664 std::vector<std::string> config_strings;
665 boost::split(config_strings, args, boost::algorithm::is_any_of(";"));
666 for (auto iter = config_strings.begin(); iter != config_strings.end();)
667 if (iter->empty())
668 iter = config_strings.erase(iter);
669 else
670 ++iter;
671
672 do_tests(1, tests, config_strings);
673 do_tests(4, tests, config_strings);
674 do_tests(8, tests, config_strings);
675 // do_tests (16, tests, config_strings);
676 }
677};
678
679BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(Timing, nodestore, xrpl, 1);
680
681} // namespace NodeStore
682} // 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:144
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:147
std::string const & arg() const
Return the argument associated with the runner.
Definition suite.h:276
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(void const *key, 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:494
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:10
bool isSame(std::shared_ptr< NodeObject > const &lhs, std::shared_ptr< NodeObject > const &rhs)
Returns true if objects are identical.
Definition TestBase.h:37
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.
constexpr auto megabytes(T value) noexcept
void Rethrow()
Rethrow the exception currently being handled.
Definition contract.h:28
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)