rippled
beast_io_latency_probe_test.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2018 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 #include <ripple/beast/asio/io_latency_probe.h>
20 #include <ripple/beast/unit_test.h>
21 
22 #include <ripple/beast/test/yield_to.hpp>
23 
24 #include <boost/asio/basic_waitable_timer.hpp>
25 #include <boost/asio/deadline_timer.hpp>
26 #include <boost/asio/io_service.hpp>
27 
28 #include <algorithm>
29 #include <chrono>
30 #include <mutex>
31 #include <numeric>
32 #include <optional>
33 #include <thread>
34 #include <vector>
35 
36 using namespace std::chrono_literals;
37 
38 class io_latency_probe_test : public beast::unit_test::suite,
39  public beast::test::enable_yield_to
40 {
41  using MyTimer =
42  boost::asio::basic_waitable_timer<std::chrono::steady_clock>;
43 
44 #ifdef RIPPLED_RUNNING_IN_CI
45 
52  template <
53  class Clock,
54  class MeasureClock = std::chrono::high_resolution_clock>
55  struct measure_asio_timers
56  {
57  using duration = typename Clock::duration;
58  using rep = typename MeasureClock::duration::rep;
59 
60  std::vector<duration> elapsed_times_;
61 
62  measure_asio_timers(duration interval = 100ms, size_t num_samples = 50)
63  {
64  using namespace std::chrono;
65  boost::asio::io_service ios;
67  std::thread worker{[&] { ios.run(); }};
68  boost::asio::basic_waitable_timer<Clock> timer{ios};
69  elapsed_times_.reserve(num_samples);
70  std::mutex mtx;
71  std::unique_lock<std::mutex> mainlock{mtx};
73  bool done = false;
74  boost::system::error_code wait_err;
75 
76  while (--num_samples)
77  {
78  auto const start{MeasureClock::now()};
79  done = false;
80  timer.expires_after(interval);
81  timer.async_wait([&](boost::system::error_code const& ec) {
82  if (ec)
83  wait_err = ec;
84  auto const end{MeasureClock::now()};
85  elapsed_times_.emplace_back(end - start);
86  std::lock_guard lk{mtx};
87  done = true;
88  cv.notify_one();
89  });
90  cv.wait(mainlock, [&done] { return done; });
91  }
92  work.reset();
93  worker.join();
94  if (wait_err)
95  boost::asio::detail::throw_error(wait_err, "wait");
96  }
97 
98  template <class D>
99  auto
100  getMean()
101  {
102  double sum = {0};
103  for (auto const& v : elapsed_times_)
104  {
105  sum += static_cast<double>(
106  std::chrono::duration_cast<D>(v).count());
107  }
108  return sum / elapsed_times_.size();
109  }
110 
111  template <class D>
112  auto
113  getMax()
114  {
115  return std::chrono::duration_cast<D>(
117  elapsed_times_.begin(), elapsed_times_.end()))
118  .count();
119  }
120 
121  template <class D>
122  auto
123  getMin()
124  {
125  return std::chrono::duration_cast<D>(
127  elapsed_times_.begin(), elapsed_times_.end()))
128  .count();
129  }
130  };
131 #endif
132 
134  {
137 
139  std::chrono::milliseconds interval,
140  boost::asio::io_service& ios)
141  : probe_(interval, ios)
142  {
143  }
144 
145  void
147  {
148  probe_.sample(std::ref(*this));
149  }
150 
151  void
153  {
154  probe_.sample_one(std::ref(*this));
155  }
156 
157  void
158  operator()(std::chrono::steady_clock::duration const& elapsed)
159  {
160  durations_.push_back(elapsed);
161  }
162  };
163 
164  void
165  testSampleOne(boost::asio::yield_context& yield)
166  {
167  testcase << "sample one";
168  boost::system::error_code ec;
169  test_sampler io_probe{100ms, get_io_service()};
170  io_probe.start_one();
171  MyTimer timer{get_io_service(), 1s};
172  timer.async_wait(yield[ec]);
173  if (!BEAST_EXPECTS(!ec, ec.message()))
174  return;
175  BEAST_EXPECT(io_probe.durations_.size() == 1);
176  io_probe.probe_.cancel_async();
177  }
178 
179  void
180  testSampleOngoing(boost::asio::yield_context& yield)
181  {
182  testcase << "sample ongoing";
183  boost::system::error_code ec;
184  using namespace std::chrono;
185  auto interval = 99ms;
186  auto probe_duration = 1s;
187 
188  size_t expected_probe_count_max = (probe_duration / interval);
189  size_t expected_probe_count_min = expected_probe_count_max;
190 #ifdef RIPPLED_RUNNING_IN_CI
191  // adjust min expected based on measurements
192  // if running in CI/VM environment
193  measure_asio_timers<steady_clock> tt{interval};
194  log << "measured mean for timers: " << tt.getMean<milliseconds>()
195  << "ms\n";
196  log << "measured max for timers: " << tt.getMax<milliseconds>()
197  << "ms\n";
198  expected_probe_count_min =
199  static_cast<size_t>(
200  duration_cast<milliseconds>(probe_duration).count()) /
201  static_cast<size_t>(tt.getMean<milliseconds>());
202 #endif
203  test_sampler io_probe{interval, get_io_service()};
204  io_probe.start();
205  MyTimer timer{get_io_service(), probe_duration};
206  timer.async_wait(yield[ec]);
207  if (!BEAST_EXPECTS(!ec, ec.message()))
208  return;
209  auto probes_seen = io_probe.durations_.size();
210  BEAST_EXPECTS(
211  probes_seen >= (expected_probe_count_min - 1) &&
212  probes_seen <= (expected_probe_count_max + 1),
213  std::string("probe count is ") + std::to_string(probes_seen));
214  io_probe.probe_.cancel_async();
215  // wait again in order to flush the remaining
216  // probes from the work queue
217  timer.expires_from_now(1s);
218  timer.async_wait(yield[ec]);
219  }
220 
221  void
222  testCanceled(boost::asio::yield_context& yield)
223  {
224  testcase << "canceled";
225  test_sampler io_probe{100ms, get_io_service()};
226  io_probe.probe_.cancel_async();
227  except<std::logic_error>([&io_probe]() { io_probe.start_one(); });
228  except<std::logic_error>([&io_probe]() { io_probe.start(); });
229  }
230 
231 public:
232  void
233  run() override
234  {
235  yield_to([&](boost::asio::yield_context& yield) {
236  testSampleOne(yield);
237  testSampleOngoing(yield);
238  testCanceled(yield);
239  });
240  }
241 };
242 
243 BEAST_DEFINE_TESTSUITE(io_latency_probe, asio, beast);
io_latency_probe_test::test_sampler::start
void start()
Definition: beast_io_latency_probe_test.cpp:146
std::max_element
T max_element(T... args)
std::string
STL class.
ripple::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(AccountTxPaging, app, ripple)
io_latency_probe_test::testCanceled
void testCanceled(boost::asio::yield_context &yield)
Definition: beast_io_latency_probe_test.cpp:222
std::vector::reserve
T reserve(T... args)
io_latency_probe_test::test_sampler::probe_
beast::io_latency_probe< std::chrono::steady_clock > probe_
Definition: beast_io_latency_probe_test.cpp:135
vector
std::chrono::milliseconds
std::lock_guard
STL class.
io_latency_probe_test::testSampleOne
void testSampleOne(boost::asio::yield_context &yield)
Definition: beast_io_latency_probe_test.cpp:165
io_latency_probe_test::test_sampler::start_one
void start_one()
Definition: beast_io_latency_probe_test.cpp:152
algorithm
std::chrono::high_resolution_clock
std::vector::push_back
T push_back(T... args)
io_latency_probe_test::test_sampler::durations_
std::vector< std::chrono::steady_clock::duration > durations_
Definition: beast_io_latency_probe_test.cpp:136
io_latency_probe_test::test_sampler
Definition: beast_io_latency_probe_test.cpp:133
std::log
T log(T... args)
thread
io_latency_probe_test::run
void run() override
Definition: beast_io_latency_probe_test.cpp:233
chrono
std::min_element
T min_element(T... args)
std::unique_lock
STL class.
beast::io_latency_probe::sample
void sample(Handler &&handler)
Initiate continuous i/o latency sampling.
Definition: io_latency_probe.h:119
beast::io_latency_probe::sample_one
void sample_one(Handler &&handler)
Measure one sample of i/o latency.
Definition: io_latency_probe.h:104
std::to_string
T to_string(T... args)
io_latency_probe_test::testSampleOngoing
void testSampleOngoing(boost::asio::yield_context &yield)
Definition: beast_io_latency_probe_test.cpp:180
std::condition_variable::wait
T wait(T... args)
beast::io_latency_probe< std::chrono::steady_clock >
io_latency_probe_test::test_sampler::test_sampler
test_sampler(std::chrono::milliseconds interval, boost::asio::io_service &ios)
Definition: beast_io_latency_probe_test.cpp:138
io_latency_probe_test::MyTimer
boost::asio::basic_waitable_timer< std::chrono::steady_clock > MyTimer
Definition: beast_io_latency_probe_test.cpp:42
std::condition_variable::notify_one
T notify_one(T... args)
std::vector::emplace_back
T emplace_back(T... args)
std::vector::begin
T begin(T... args)
std::condition_variable
io_latency_probe_test::test_sampler::operator()
void operator()(std::chrono::steady_clock::duration const &elapsed)
Definition: beast_io_latency_probe_test.cpp:158
optional
beast::io_latency_probe::cancel_async
void cancel_async()
Definition: io_latency_probe.h:91
mutex
std::end
T end(T... args)
numeric
io_latency_probe_test
Definition: beast_io_latency_probe_test.cpp:38
std::ref
T ref(T... args)
beast
Definition: base_uint.h:641
std::chrono