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