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 #include <boost/asio/basic_waitable_timer.hpp>
22 #include <boost/asio/deadline_timer.hpp>
23 #include <boost/asio/io_service.hpp>
24 #include <boost/optional.hpp>
25 #include <algorithm>
26 #include <beast/test/yield_to.hpp>
27 #include <chrono>
28 #include <mutex>
29 #include <numeric>
30 #include <thread>
31 #include <vector>
32 
33 using namespace std::chrono_literals;
34 
35 class io_latency_probe_test : public beast::unit_test::suite,
36  public beast::test::enable_yield_to
37 {
38  using MyTimer =
39  boost::asio::basic_waitable_timer<std::chrono::steady_clock>;
40 
41 #ifdef RIPPLED_RUNNING_IN_CI
42 
49  template <
50  class Clock,
51  class MeasureClock = std::chrono::high_resolution_clock>
52  struct measure_asio_timers
53  {
54  using duration = typename Clock::duration;
55  using rep = typename MeasureClock::duration::rep;
56 
57  std::vector<duration> elapsed_times_;
58 
59  measure_asio_timers(duration interval = 100ms, size_t num_samples = 50)
60  {
61  using namespace std::chrono;
62  boost::asio::io_service ios;
63  boost::optional<boost::asio::io_service::work> work{ios};
64  std::thread worker{[&] { ios.run(); }};
65  boost::asio::basic_waitable_timer<Clock> timer{ios};
66  elapsed_times_.reserve(num_samples);
67  std::mutex mtx;
68  std::unique_lock<std::mutex> mainlock{mtx};
70  bool done = false;
71  boost::system::error_code wait_err;
72 
73  while (--num_samples)
74  {
75  auto const start{MeasureClock::now()};
76  done = false;
77  timer.expires_after(interval);
78  timer.async_wait([&](boost::system::error_code const& ec) {
79  if (ec)
80  wait_err = ec;
81  auto const end{MeasureClock::now()};
82  elapsed_times_.emplace_back(end - start);
83  std::lock_guard lk{mtx};
84  done = true;
85  cv.notify_one();
86  });
87  cv.wait(mainlock, [&done] { return done; });
88  }
89  work = boost::none;
90  worker.join();
91  if (wait_err)
92  boost::asio::detail::throw_error(wait_err, "wait");
93  }
94 
95  template <class D>
96  auto
97  getMean()
98  {
99  double sum = {0};
100  for (auto const& v : elapsed_times_)
101  {
102  sum += static_cast<double>(
103  std::chrono::duration_cast<D>(v).count());
104  }
105  return sum / elapsed_times_.size();
106  }
107 
108  template <class D>
109  auto
110  getMax()
111  {
112  return std::chrono::duration_cast<D>(
114  elapsed_times_.begin(), elapsed_times_.end()))
115  .count();
116  }
117 
118  template <class D>
119  auto
120  getMin()
121  {
122  return std::chrono::duration_cast<D>(
124  elapsed_times_.begin(), elapsed_times_.end()))
125  .count();
126  }
127  };
128 #endif
129 
131  {
134 
136  std::chrono::milliseconds interval,
137  boost::asio::io_service& ios)
138  : probe_(interval, ios)
139  {
140  }
141 
142  void
144  {
145  probe_.sample(std::ref(*this));
146  }
147 
148  void
150  {
151  probe_.sample_one(std::ref(*this));
152  }
153 
154  void
155  operator()(std::chrono::steady_clock::duration const& elapsed)
156  {
157  durations_.push_back(elapsed);
158  }
159  };
160 
161  void
162  testSampleOne(boost::asio::yield_context& yield)
163  {
164  testcase << "sample one";
165  boost::system::error_code ec;
166  test_sampler io_probe{100ms, get_io_service()};
167  io_probe.start_one();
168  MyTimer timer{get_io_service(), 1s};
169  timer.async_wait(yield[ec]);
170  if (!BEAST_EXPECTS(!ec, ec.message()))
171  return;
172  BEAST_EXPECT(io_probe.durations_.size() == 1);
173  io_probe.probe_.cancel_async();
174  }
175 
176  void
177  testSampleOngoing(boost::asio::yield_context& yield)
178  {
179  testcase << "sample ongoing";
180  boost::system::error_code ec;
181  using namespace std::chrono;
182  auto interval = 99ms;
183  auto probe_duration = 1s;
184 
185  size_t expected_probe_count_max = (probe_duration / interval);
186  size_t expected_probe_count_min = expected_probe_count_max;
187 #ifdef RIPPLED_RUNNING_IN_CI
188  // adjust min expected based on measurements
189  // if running in CI/VM environment
190  measure_asio_timers<steady_clock> tt{interval};
191  log << "measured mean for timers: " << tt.getMean<milliseconds>()
192  << "ms\n";
193  log << "measured max for timers: " << tt.getMax<milliseconds>()
194  << "ms\n";
195  expected_probe_count_min =
196  static_cast<size_t>(
197  duration_cast<milliseconds>(probe_duration).count()) /
198  static_cast<size_t>(tt.getMean<milliseconds>());
199 #endif
200  log << "expected_probe_count_min: " << expected_probe_count_min << "\n";
201  log << "expected_probe_count_max: " << expected_probe_count_max << "\n";
202 
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:143
std::max_element
T max_element(T... args)
ripple::test::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(AccountDelete, app, ripple)
std::string
STL class.
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:132
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:162
io_latency_probe_test::test_sampler::start_one
void start_one()
Definition: beast_io_latency_probe_test.cpp:149
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:133
io_latency_probe_test::test_sampler
Definition: beast_io_latency_probe_test.cpp:130
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:177
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:135
io_latency_probe_test::MyTimer
boost::asio::basic_waitable_timer< std::chrono::steady_clock > MyTimer
Definition: beast_io_latency_probe_test.cpp:39
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:155
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:35
std::ref
T ref(T... args)
beast
Definition: base_uint.h:585
std::chrono