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  log << "expected_probe_count_min: " << expected_probe_count_min << "\n";
204  log << "expected_probe_count_max: " << expected_probe_count_max << "\n";
205 
206  test_sampler io_probe{interval, get_io_service()};
207  io_probe.start();
208  MyTimer timer{get_io_service(), probe_duration};
209  timer.async_wait(yield[ec]);
210  if (!BEAST_EXPECTS(!ec, ec.message()))
211  return;
212  auto probes_seen = io_probe.durations_.size();
213  BEAST_EXPECTS(
214  probes_seen >= (expected_probe_count_min - 1) &&
215  probes_seen <= (expected_probe_count_max + 1),
216  std::string("probe count is ") + std::to_string(probes_seen));
217  io_probe.probe_.cancel_async();
218  // wait again in order to flush the remaining
219  // probes from the work queue
220  timer.expires_from_now(1s);
221  timer.async_wait(yield[ec]);
222  }
223 
224  void
225  testCanceled(boost::asio::yield_context& yield)
226  {
227  testcase << "canceled";
228  test_sampler io_probe{100ms, get_io_service()};
229  io_probe.probe_.cancel_async();
230  except<std::logic_error>([&io_probe]() { io_probe.start_one(); });
231  except<std::logic_error>([&io_probe]() { io_probe.start(); });
232  }
233 
234 public:
235  void
236  run() override
237  {
238  yield_to([&](boost::asio::yield_context& yield) {
239  testSampleOne(yield);
240  testSampleOngoing(yield);
241  testCanceled(yield);
242  });
243  }
244 };
245 
246 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:225
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:236
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:657
std::chrono