rippled
Loading...
Searching...
No Matches
Timing_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012, 2013 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <test/nodestore/TestBase.h>
21#include <test/unit_test/SuiteJournal.h>
22#include <xrpld/nodestore/DummyScheduler.h>
23#include <xrpld/nodestore/Manager.h>
24#include <xrpl/basics/BasicConfig.h>
25#include <xrpl/basics/ByteUtilities.h>
26#include <xrpl/basics/safe_cast.h>
27#include <xrpl/beast/unit_test.h>
28#include <xrpl/beast/unit_test/thread.h>
29#include <xrpl/beast/utility/temp_dir.h>
30#include <xrpl/beast/xor_shift_engine.h>
31
32#include <boost/algorithm/string.hpp>
33
34#include <atomic>
35#include <chrono>
36#include <iterator>
37#include <limits>
38#include <random>
39#include <sstream>
40#include <stdexcept>
41#include <type_traits>
42#include <utility>
43
44#ifndef NODESTORE_TIMING_DO_VERIFY
45#define NODESTORE_TIMING_DO_VERIFY 0
46#endif
47
48namespace ripple {
49namespace NodeStore {
50
53 Section const& config,
54 Scheduler& scheduler,
55 beast::Journal journal)
56{
58 config, megabytes(4), scheduler, journal);
59}
60
61// Fill memory with random bits
62template <class Generator>
63static void
64rngcpy(void* buffer, std::size_t bytes, Generator& g)
65{
66 using result_type = typename Generator::result_type;
67 while (bytes >= sizeof(result_type))
68 {
69 auto const v = g();
70 memcpy(buffer, &v, sizeof(v));
71 buffer = reinterpret_cast<std::uint8_t*>(buffer) + sizeof(v);
72 bytes -= sizeof(v);
73 }
74
75 if (bytes > 0)
76 {
77 auto const v = g();
78 memcpy(buffer, &v, bytes);
79 }
80}
81
82// Instance of node factory produces a deterministic sequence
83// of random NodeObjects within the given
85{
86private:
87 enum { minLedger = 1, maxLedger = 1000000, minSize = 250, maxSize = 1250 };
88
93
94public:
95 explicit Sequence(std::uint8_t prefix)
96 : prefix_(prefix)
97 // uniform distribution over hotLEDGER - hotTRANSACTION_NODE
98 // but exclude hotTRANSACTION = 2 (removed)
99 , d_type_({1, 1, 0, 1, 1})
101 {
102 }
103
104 // Returns the n-th key
105 uint256
107 {
108 gen_.seed(n + 1);
109 uint256 result;
110 rngcpy(&*result.begin(), result.size(), gen_);
111 return result;
112 }
113
114 // Returns the n-th complete NodeObject
117 {
118 gen_.seed(n + 1);
119 uint256 key;
120 auto const data = static_cast<std::uint8_t*>(&*key.begin());
121 *data = prefix_;
122 rngcpy(data + 1, key.size() - 1, gen_);
123 Blob value(d_size_(gen_));
124 rngcpy(&value[0], value.size(), gen_);
126 safe_cast<NodeObjectType>(d_type_(gen_)), std::move(value), key);
127 }
128
129 // returns a batch of NodeObjects starting at n
130 void
132 {
133 b.clear();
134 b.reserve(size);
135 while (size--)
136 b.emplace_back(obj(n++));
137 }
138};
139
140//----------------------------------------------------------------------------------
141
143{
144public:
145 enum {
146 // percent of fetches for missing nodes
148 };
149
151#ifndef NDEBUG
153#else
154 std::size_t const default_items = 100000; // release
155#endif
156
159
160 struct Params
161 {
164 };
165
166 static std::string
167 to_string(Section const& config)
168 {
169 std::string s;
170 for (auto iter = config.begin(); iter != config.end(); ++iter)
171 s += (iter != config.begin() ? "," : "") + iter->first + "=" +
172 iter->second;
173 return s;
174 }
175
176 static std::string
178 {
180 ss << std::fixed << std::setprecision(3) << (d.count() / 1000.) << "s";
181 return ss.str();
182 }
183
184 static Section
186 {
187 Section section;
189 boost::split(v, s, boost::algorithm::is_any_of(","));
190 section.append(v);
191 return section;
192 }
193
194 //--------------------------------------------------------------------------
195
196 // Workaround for GCC's parameter pack expansion in lambdas
197 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47226
198 template <class Body>
200 {
201 private:
204
205 public:
207 : n_(n), c_(c)
208 {
209 }
210
211 template <class... Args>
212 void
213 operator()(Args&&... args)
214 {
215 Body body(args...);
216 for (;;)
217 {
218 auto const i = c_++;
219 if (i >= n_)
220 break;
221 body(i);
222 }
223 }
224 };
225
226 /* Execute parallel-for loop.
227
228 Constructs `number_of_threads` instances of `Body`
229 with `args...` parameters and runs them on individual threads
230 with unique loop indexes in the range [0, n).
231 */
232 template <class Body, class... Args>
233 void
235 std::size_t const n,
236 std::size_t number_of_threads,
237 Args const&... args)
238 {
241 t.reserve(number_of_threads);
242 for (std::size_t id = 0; id < number_of_threads; ++id)
243 t.emplace_back(*this, parallel_for_lambda<Body>(n, c), args...);
244 for (auto& _ : t)
245 _.join();
246 }
247
248 template <class Body, class... Args>
249 void
251 std::size_t const n,
252 std::size_t number_of_threads,
253 Args const&... args)
254 {
257 t.reserve(number_of_threads);
258 for (std::size_t id = 0; id < number_of_threads; ++id)
259 t.emplace_back(*this, parallel_for_lambda<Body>(n, c), id, args...);
260 for (auto& _ : t)
261 _.join();
262 }
263
264 //--------------------------------------------------------------------------
265
266 // Insert only
267 void
269 Section const& config,
270 Params const& params,
271 beast::Journal journal)
272 {
273 DummyScheduler scheduler;
274 auto backend = make_Backend(config, scheduler, journal);
275 BEAST_EXPECT(backend != nullptr);
276 backend->open();
277
278 class Body
279 {
280 private:
281 suite& suite_;
282 Backend& backend_;
283 Sequence seq_;
284
285 public:
286 explicit Body(suite& s, Backend& backend)
287 : suite_(s), backend_(backend), seq_(1)
288 {
289 }
290
291 void
292 operator()(std::size_t i)
293 {
294 try
295 {
296 backend_.store(seq_.obj(i));
297 }
298 catch (std::exception const& e)
299 {
300 suite_.fail(e.what());
301 }
302 }
303 };
304
305 try
306 {
307 parallel_for<Body>(
308 params.items,
309 params.threads,
310 std::ref(*this),
311 std::ref(*backend));
312 }
313 catch (std::exception const&)
314 {
315#if NODESTORE_TIMING_DO_VERIFY
316 backend->verify();
317#endif
318 Rethrow();
319 }
320 backend->close();
321 }
322
323 // Fetch existing keys
324 void
326 Section const& config,
327 Params const& params,
328 beast::Journal journal)
329 {
330 DummyScheduler scheduler;
331 auto backend = make_Backend(config, scheduler, journal);
332 BEAST_EXPECT(backend != nullptr);
333 backend->open();
334
335 class Body
336 {
337 private:
338 suite& suite_;
339 Backend& backend_;
340 Sequence seq1_;
343
344 public:
345 Body(
346 std::size_t id,
347 suite& s,
348 Params const& params,
349 Backend& backend)
350 : suite_(s)
351 , backend_(backend)
352 , seq1_(1)
353 , gen_(id + 1)
354 , dist_(0, params.items - 1)
355 {
356 }
357
358 void
359 operator()(std::size_t i)
360 {
361 try
362 {
365 obj = seq1_.obj(dist_(gen_));
366 backend_.fetch(obj->getHash().data(), &result);
367 suite_.expect(result && isSame(result, obj));
368 }
369 catch (std::exception const& e)
370 {
371 suite_.fail(e.what());
372 }
373 }
374 };
375 try
376 {
377 parallel_for_id<Body>(
378 params.items,
379 params.threads,
380 std::ref(*this),
381 std::ref(params),
382 std::ref(*backend));
383 }
384 catch (std::exception const&)
385 {
386#if NODESTORE_TIMING_DO_VERIFY
387 backend->verify();
388#endif
389 Rethrow();
390 }
391 backend->close();
392 }
393
394 // Perform lookups of non-existent keys
395 void
397 Section const& config,
398 Params const& params,
399 beast::Journal journal)
400 {
401 DummyScheduler scheduler;
402 auto backend = make_Backend(config, scheduler, journal);
403 BEAST_EXPECT(backend != nullptr);
404 backend->open();
405
406 class Body
407 {
408 private:
409 suite& suite_;
410 // Params const& params_;
411 Backend& backend_;
412 Sequence seq2_;
415
416 public:
417 Body(
418 std::size_t id,
419 suite& s,
420 Params const& params,
421 Backend& backend)
422 : suite_(s)
423 //, params_ (params)
424 , backend_(backend)
425 , seq2_(2)
426 , gen_(id + 1)
427 , dist_(0, params.items - 1)
428 {
429 }
430
431 void
432 operator()(std::size_t i)
433 {
434 try
435 {
436 auto const key = seq2_.key(i);
438 backend_.fetch(key.data(), &result);
439 suite_.expect(!result);
440 }
441 catch (std::exception const& e)
442 {
443 suite_.fail(e.what());
444 }
445 }
446 };
447
448 try
449 {
450 parallel_for_id<Body>(
451 params.items,
452 params.threads,
453 std::ref(*this),
454 std::ref(params),
455 std::ref(*backend));
456 }
457 catch (std::exception const&)
458 {
459#if NODESTORE_TIMING_DO_VERIFY
460 backend->verify();
461#endif
462 Rethrow();
463 }
464 backend->close();
465 }
466
467 // Fetch with present and missing keys
468 void
470 Section const& config,
471 Params const& params,
472 beast::Journal journal)
473 {
474 DummyScheduler scheduler;
475 auto backend = make_Backend(config, scheduler, journal);
476 BEAST_EXPECT(backend != nullptr);
477 backend->open();
478
479 class Body
480 {
481 private:
482 suite& suite_;
483 // Params const& params_;
484 Backend& backend_;
485 Sequence seq1_;
486 Sequence seq2_;
490
491 public:
492 Body(
493 std::size_t id,
494 suite& s,
495 Params const& params,
496 Backend& backend)
497 : suite_(s)
498 //, params_ (params)
499 , backend_(backend)
500 , seq1_(1)
501 , seq2_(2)
502 , gen_(id + 1)
503 , rand_(0, 99)
504 , dist_(0, params.items - 1)
505 {
506 }
507
508 void
509 operator()(std::size_t i)
510 {
511 try
512 {
513 if (rand_(gen_) < missingNodePercent)
514 {
515 auto const key = seq2_.key(dist_(gen_));
517 backend_.fetch(key.data(), &result);
518 suite_.expect(!result);
519 }
520 else
521 {
524 obj = seq1_.obj(dist_(gen_));
525 backend_.fetch(obj->getHash().data(), &result);
526 suite_.expect(result && isSame(result, obj));
527 }
528 }
529 catch (std::exception const& e)
530 {
531 suite_.fail(e.what());
532 }
533 }
534 };
535
536 try
537 {
538 parallel_for_id<Body>(
539 params.items,
540 params.threads,
541 std::ref(*this),
542 std::ref(params),
543 std::ref(*backend));
544 }
545 catch (std::exception const&)
546 {
547#if NODESTORE_TIMING_DO_VERIFY
548 backend->verify();
549#endif
550 Rethrow();
551 }
552 backend->close();
553 }
554
555 // Simulate a rippled workload:
556 // Each thread randomly:
557 // inserts a new key
558 // fetches an old key
559 // fetches recent, possibly non existent data
560 void
561 do_work(Section const& config, Params const& params, beast::Journal journal)
562 {
563 DummyScheduler scheduler;
564 auto backend = make_Backend(config, scheduler, journal);
565 BEAST_EXPECT(backend != nullptr);
566 backend->setDeletePath();
567 backend->open();
568
569 class Body
570 {
571 private:
572 suite& suite_;
573 Params const& params_;
574 Backend& backend_;
575 Sequence seq1_;
580
581 public:
582 Body(
583 std::size_t id,
584 suite& s,
585 Params const& params,
586 Backend& backend)
587 : suite_(s)
588 , params_(params)
589 , backend_(backend)
590 , seq1_(1)
591 , gen_(id + 1)
592 , rand_(0, 99)
593 , recent_(params.items, params.items * 2 - 1)
594 , older_(0, params.items - 1)
595 {
596 }
597
598 void
599 operator()(std::size_t i)
600 {
601 try
602 {
603 if (rand_(gen_) < 200)
604 {
605 // historical lookup
608 auto const j = older_(gen_);
609 obj = seq1_.obj(j);
611 backend_.fetch(obj->getHash().data(), &result);
612 suite_.expect(result != nullptr);
613 suite_.expect(isSame(result, obj));
614 }
615
616 char p[2];
617 p[0] = rand_(gen_) < 50 ? 0 : 1;
618 p[1] = 1 - p[0];
619 for (int q = 0; q < 2; ++q)
620 {
621 switch (p[q])
622 {
623 case 0: {
624 // fetch recent
627 auto const j = recent_(gen_);
628 obj = seq1_.obj(j);
629 backend_.fetch(obj->getHash().data(), &result);
630 suite_.expect(!result || isSame(result, obj));
631 break;
632 }
633
634 case 1: {
635 // insert new
636 auto const j = i + params_.items;
637 backend_.store(seq1_.obj(j));
638 break;
639 }
640 }
641 }
642 }
643 catch (std::exception const& e)
644 {
645 suite_.fail(e.what());
646 }
647 }
648 };
649
650 try
651 {
652 parallel_for_id<Body>(
653 params.items,
654 params.threads,
655 std::ref(*this),
656 std::ref(params),
657 std::ref(*backend));
658 }
659 catch (std::exception const&)
660 {
661#if NODESTORE_TIMING_DO_VERIFY
662 backend->verify();
663#endif
664 Rethrow();
665 }
666 backend->close();
667 }
668
669 //--------------------------------------------------------------------------
670
671 using test_func =
672 void (Timing_test::*)(Section const&, Params const&, beast::Journal);
674
677 test_func f,
678 Section const& config,
679 Params const& params,
680 beast::Journal journal)
681 {
682 auto const start = clock_type::now();
683 (this->*f)(config, params, journal);
684 return std::chrono::duration_cast<duration_type>(
685 clock_type::now() - start);
686 }
687
688 void
690 std::size_t threads,
691 test_list const& tests,
692 std::vector<std::string> const& config_strings)
693 {
694 using std::setw;
695 int w = 8;
696 for (auto const& test : tests)
697 if (w < test.first.size())
698 w = test.first.size();
699 log << threads << " Thread" << (threads > 1 ? "s" : "") << ", "
700 << default_items << " Objects" << std::endl;
701 {
703 ss << std::left << setw(10) << "Backend" << std::right;
704 for (auto const& test : tests)
705 ss << " " << setw(w) << test.first;
706 log << ss.str() << std::endl;
707 }
708
709 using namespace beast::severities;
710 test::SuiteJournal journal("Timing_test", *this);
711
712 for (auto const& config_string : config_strings)
713 {
714 Params params;
715 params.items = default_items;
716 params.threads = threads;
717 for (auto i = default_repeat; i--;)
718 {
719 beast::temp_dir tempDir;
720 Section config = parse(config_string);
721 config.set("path", tempDir.path());
723 ss << std::left << setw(10)
724 << get(config, "type", std::string()) << std::right;
725 for (auto const& test : tests)
726 ss << " " << setw(w)
727 << to_string(
728 do_test(test.second, config, params, journal));
729 ss << " " << to_string(config);
730 log << ss.str() << std::endl;
731 }
732 }
733 }
734
735 void
736 run() override
737 {
739
740 /* Parameters:
741
742 repeat Number of times to repeat each test
743 items Number of objects to create in the database
744
745 */
746 std::string default_args =
747 "type=nudb"
748#if RIPPLE_ROCKSDB_AVAILABLE
749 ";type=rocksdb,open_files=2000,filter_bits=12,cache_mb=256,"
750 "file_size_mb=8,file_size_mult=2"
751#endif
752#if 0
753 ";type=memory|path=NodeStore"
754#endif
755 ;
756
757 test_list const tests = {
758 {"Insert", &Timing_test::do_insert},
759 {"Fetch", &Timing_test::do_fetch},
760 {"Missing", &Timing_test::do_missing},
761 {"Mixed", &Timing_test::do_mixed},
762 {"Work", &Timing_test::do_work}};
763
764 auto args = arg().empty() ? default_args : arg();
765 std::vector<std::string> config_strings;
766 boost::split(config_strings, args, boost::algorithm::is_any_of(";"));
767 for (auto iter = config_strings.begin(); iter != config_strings.end();)
768 if (iter->empty())
769 iter = config_strings.erase(iter);
770 else
771 ++iter;
772
773 do_tests(1, tests, config_strings);
774 do_tests(4, tests, config_strings);
775 do_tests(8, tests, config_strings);
776 // do_tests (16, tests, config_strings);
777 }
778};
779
780BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(Timing, NodeStore, ripple, 1);
781
782} // namespace NodeStore
783} // namespace ripple
T begin(T... args)
A generic endpoint for log messages.
Definition: Journal.h:60
void seed(result_type seed)
RAII temporary directory.
Definition: temp_dir.h:35
std::string path() const
Get the native path for the temporary directory.
Definition: temp_dir.h:67
A testsuite class.
Definition: suite.h:55
log_os< char > log
Logging output stream.
Definition: suite.h:152
testcase_t testcase
Memberspace for declaring test cases.
Definition: suite.h:155
std::string const & arg() const
Return the argument associated with the runner.
Definition: suite.h:288
static std::shared_ptr< NodeObject > createObject(NodeObjectType type, Blob &&data, uint256 const &hash)
Create an object from fields.
Definition: NodeObject.cpp:38
A backend used for the NodeStore.
Definition: Backend.h:40
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.
Definition: ManagerImp.cpp:116
Scheduling for asynchronous backend activity.
std::discrete_distribution< std::uint32_t > d_type_
Definition: Timing_test.cpp:91
Sequence(std::uint8_t prefix)
Definition: Timing_test.cpp:95
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_
Definition: Timing_test.cpp:89
std::uniform_int_distribution< std::uint32_t > d_size_
Definition: Timing_test.cpp:92
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)
std::size_t const default_repeat
std::size_t const default_items
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:45
void set(std::string const &key, std::string const &value)
Set a key/value pair.
Definition: BasicConfig.cpp:41
const_iterator begin() const
Definition: BasicConfig.h:184
void append(std::vector< std::string > const &lines)
Append a set of lines to this section.
Definition: BasicConfig.cpp:47
const_iterator end() const
Definition: BasicConfig.h:198
iterator begin()
Definition: base_uint.h:136
static constexpr std::size_t size()
Definition: base_uint.h:526
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:30
std::unique_ptr< Backend > make_Backend(Section const &config, Scheduler &scheduler, beast::Journal journal)
Definition: Timing_test.cpp:52
static void rngcpy(void *buffer, std::size_t bytes, Generator &g)
Definition: Timing_test.cpp:64
bool isSame(std::shared_ptr< NodeObject > const &lhs, std::shared_ptr< NodeObject > const &rhs)
Returns true if objects are identical.
Definition: TestBase.h:59
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
constexpr auto megabytes(T value) noexcept
Definition: ByteUtilities.h:34
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
Definition: BasicConfig.h:355
void Rethrow()
Rethrow the exception currently being handled.
Definition: contract.h:48
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)