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