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