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