rippled
Loading...
Searching...
No Matches
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 <xrpl/beast/asio/io_latency_probe.h>
20#include <xrpl/beast/unit_test.h>
21
22#include <xrpl/beast/test/yield_to.h>
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
36using namespace std::chrono_literals;
37
40{
41 using MyTimer =
42 boost::asio::basic_waitable_timer<std::chrono::steady_clock>;
43
44#ifdef RIPPLED_RUNNING_IN_CI
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
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
231public:
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
243BEAST_DEFINE_TESTSUITE(io_latency_probe, asio, beast);
T begin(T... args)
Measures handler latency on an io_service queue.
void sample(Handler &&handler)
Initiate continuous i/o latency sampling.
void sample_one(Handler &&handler)
Measure one sample of i/o latency.
Mix-in to support tests using asio coroutines.
Definition: yield_to.h:30
void yield_to(F0 &&f0, FN &&... fn)
Run one or more functions, each in a coroutine.
Definition: yield_to.h:100
boost::asio::io_service & get_io_service()
Return the io_service associated with the object.
Definition: yield_to.h:61
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
boost::asio::basic_waitable_timer< std::chrono::steady_clock > MyTimer
void testCanceled(boost::asio::yield_context &yield)
void testSampleOngoing(boost::asio::yield_context &yield)
void run() override
Runs the suite.
void testSampleOne(boost::asio::yield_context &yield)
T emplace_back(T... args)
T end(T... args)
T max_element(T... args)
T min_element(T... args)
T push_back(T... args)
T ref(T... args)
T reserve(T... args)
void operator()(std::chrono::steady_clock::duration const &elapsed)
test_sampler(std::chrono::milliseconds interval, boost::asio::io_service &ios)
beast::io_latency_probe< std::chrono::steady_clock > probe_
std::vector< std::chrono::steady_clock::duration > durations_
T to_string(T... args)