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