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 work{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 > 0u)
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 const 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>(
92 *std::max_element(elapsed_times_.begin(), elapsed_times_.end()))
93 .count();
94 }
95
96 template <class D>
97 auto
98 getMin()
99 {
100 return std::chrono::duration_cast<D>(
101 *std::min_element(elapsed_times_.begin(), elapsed_times_.end()))
102 .count();
103 }
104 };
105#endif
106
108 {
111
112 test_sampler(std::chrono::milliseconds interval, boost::asio::io_context& ios)
113 : probe_(interval, ios)
114 {
115 }
116
117 void
119 {
120 probe_.sample(std::ref(*this));
121 }
122
123 void
125 {
126 probe_.sample_one(std::ref(*this));
127 }
128
129 void
130 operator()(std::chrono::steady_clock::duration const& elapsed)
131 {
132 durations_.push_back(elapsed);
133 }
134 };
135
136 void
137 testSampleOne(boost::asio::yield_context& yield)
138 {
139 testcase << "sample one";
140 boost::system::error_code ec;
141 test_sampler io_probe{100ms, get_io_context()};
142 io_probe.start_one();
143 MyTimer timer{get_io_context(), 1s};
144 timer.async_wait(yield[ec]);
145 if (!BEAST_EXPECTS(!ec, ec.message()))
146 return;
147 BEAST_EXPECT(io_probe.durations_.size() == 1);
148 io_probe.probe_.cancel_async();
149 }
150
151 void
152 testSampleOngoing(boost::asio::yield_context& yield)
153 {
154 testcase << "sample ongoing";
155 boost::system::error_code ec;
156 using namespace std::chrono;
157 auto interval = 99ms;
158 auto probe_duration = 1s;
159
160 size_t const expected_probe_count_max = (probe_duration / interval);
161 // NOLINTNEXTLINE(misc-const-correctness)
162 size_t expected_probe_count_min = expected_probe_count_max;
163#ifdef XRPL_RUNNING_IN_CI
164 // adjust min expected based on measurements
165 // if running in CI/VM environment
166 measure_asio_timers<steady_clock> tt{interval};
167 log << "measured mean for timers: " << tt.getMean<milliseconds>() << "ms\n";
168 log << "measured max for timers: " << tt.getMax<milliseconds>() << "ms\n";
169 expected_probe_count_min =
170 static_cast<size_t>(duration_cast<milliseconds>(probe_duration).count()) /
171 static_cast<size_t>(tt.getMean<milliseconds>());
172#endif
173 test_sampler io_probe{interval, get_io_context()};
174 io_probe.start();
175 MyTimer timer{get_io_context(), probe_duration};
176 timer.async_wait(yield[ec]);
177 if (!BEAST_EXPECTS(!ec, ec.message()))
178 return;
179 auto probes_seen = io_probe.durations_.size();
180 BEAST_EXPECTS(
181 probes_seen >= (expected_probe_count_min - 1) &&
182 probes_seen <= (expected_probe_count_max + 1),
183 std::string("probe count is ") + std::to_string(probes_seen));
184 io_probe.probe_.cancel_async();
185 // wait again in order to flush the remaining
186 // probes from the work queue
187 timer.expires_after(1s);
188 timer.async_wait(yield[ec]);
189 }
190
191 void
192 testCanceled(boost::asio::yield_context& yield)
193 {
194 testcase << "canceled";
195 test_sampler io_probe{100ms, get_io_context()};
196 io_probe.probe_.cancel_async();
197 except<std::logic_error>([&io_probe]() { io_probe.start_one(); });
198 except<std::logic_error>([&io_probe]() { io_probe.start(); });
199 }
200
201public:
202 void
203 run() override
204 {
205 yield_to([&](boost::asio::yield_context& yield) {
206 testSampleOne(yield);
207 testSampleOngoing(yield);
208 testCanceled(yield);
209 });
210 }
211};
212
213BEAST_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:60
void yield_to(F0 &&f0, FN &&... fn)
Run one or more functions, each in a coroutine.
Definition yield_to.h:99
A testsuite class.
Definition suite.h:51
log_os< char > log
Logging output stream.
Definition suite.h:147
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:150
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)