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