]> git.proxmox.com Git - ceph.git/blob - ceph/src/test/rgw/test_rgw_throttle.cc
import ceph quincy 17.2.6
[ceph.git] / ceph / src / test / rgw / test_rgw_throttle.cc
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
3 /*
4 * Ceph - scalable distributed file system
5 *
6 * Copyright (C) 2018 Red Hat, Inc.
7 *
8 * This is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License version 2.1, as published by the Free Software
11 * Foundation. See file COPYING.
12 *
13 */
14
15 #include "rgw/rgw_aio_throttle.h"
16
17 #include <optional>
18 #include <thread>
19 #include "include/scope_guard.h"
20
21 #include <spawn/spawn.hpp>
22 #include <gtest/gtest.h>
23
24 struct RadosEnv : public ::testing::Environment {
25 public:
26 static constexpr auto poolname = "ceph_test_rgw_throttle";
27
28 static std::optional<RGWSI_RADOS> rados;
29
30 void SetUp() override {
31 rados.emplace(g_ceph_context);
32 const NoDoutPrefix no_dpp(g_ceph_context, 1);
33 ASSERT_EQ(0, rados->start(null_yield, &no_dpp));
34 int r = rados->pool({poolname}).create(&no_dpp);
35 if (r == -EEXIST)
36 r = 0;
37 ASSERT_EQ(0, r);
38 }
39 void TearDown() override {
40 rados->shutdown();
41 rados.reset();
42 }
43 };
44 std::optional<RGWSI_RADOS> RadosEnv::rados;
45
46 auto *const rados_env = ::testing::AddGlobalTestEnvironment(new RadosEnv);
47
48 // test fixture for global setup/teardown
49 class RadosFixture : public ::testing::Test {
50 protected:
51 RGWSI_RADOS::Obj make_obj(const std::string& oid) {
52 auto obj = RadosEnv::rados->obj({{RadosEnv::poolname}, oid});
53 const NoDoutPrefix no_dpp(g_ceph_context, 1);
54 ceph_assert_always(0 == obj.open(&no_dpp));
55 return obj;
56 }
57 };
58
59 using Aio_Throttle = RadosFixture;
60
61 namespace rgw {
62
63 struct scoped_completion {
64 Aio* aio = nullptr;
65 AioResult* result = nullptr;
66 ~scoped_completion() { if (aio) { complete(-ECANCELED); } }
67 void complete(int r) {
68 result->result = r;
69 aio->put(*result);
70 aio = nullptr;
71 }
72 };
73
74 auto wait_on(scoped_completion& c) {
75 return [&c] (Aio* aio, AioResult& r) { c.aio = aio; c.result = &r; };
76 }
77
78 auto wait_for(boost::asio::io_context& context, ceph::timespan duration) {
79 return [&context, duration] (Aio* aio, AioResult& r) {
80 using Clock = ceph::coarse_mono_clock;
81 using Timer = boost::asio::basic_waitable_timer<Clock>;
82 auto t = std::make_unique<Timer>(context);
83 t->expires_after(duration);
84 t->async_wait([aio, &r, t=std::move(t)] (boost::system::error_code ec) {
85 if (ec != boost::asio::error::operation_aborted) {
86 aio->put(r);
87 }
88 });
89 };
90 }
91
92 TEST_F(Aio_Throttle, NoThrottleUpToMax)
93 {
94 BlockingAioThrottle throttle(4);
95 auto obj = make_obj(__PRETTY_FUNCTION__);
96 {
97 scoped_completion op1;
98 auto c1 = throttle.get(obj, wait_on(op1), 1, 0);
99 EXPECT_TRUE(c1.empty());
100 scoped_completion op2;
101 auto c2 = throttle.get(obj, wait_on(op2), 1, 0);
102 EXPECT_TRUE(c2.empty());
103 scoped_completion op3;
104 auto c3 = throttle.get(obj, wait_on(op3), 1, 0);
105 EXPECT_TRUE(c3.empty());
106 scoped_completion op4;
107 auto c4 = throttle.get(obj, wait_on(op4), 1, 0);
108 EXPECT_TRUE(c4.empty());
109 // no completions because no ops had to wait
110 auto c5 = throttle.poll();
111 EXPECT_TRUE(c5.empty());
112 }
113 auto completions = throttle.drain();
114 ASSERT_EQ(4u, completions.size());
115 for (auto& c : completions) {
116 EXPECT_EQ(-ECANCELED, c.result);
117 }
118 }
119
120 TEST_F(Aio_Throttle, CostOverWindow)
121 {
122 BlockingAioThrottle throttle(4);
123 auto obj = make_obj(__PRETTY_FUNCTION__);
124
125 scoped_completion op;
126 auto c = throttle.get(obj, wait_on(op), 8, 0);
127 ASSERT_EQ(1u, c.size());
128 EXPECT_EQ(-EDEADLK, c.front().result);
129 }
130
131 TEST_F(Aio_Throttle, ThrottleOverMax)
132 {
133 constexpr uint64_t window = 4;
134 BlockingAioThrottle throttle(window);
135
136 auto obj = make_obj(__PRETTY_FUNCTION__);
137
138 // issue 32 writes, and verify that max_outstanding <= window
139 constexpr uint64_t total = 32;
140 uint64_t max_outstanding = 0;
141 uint64_t outstanding = 0;
142
143 // timer thread
144 boost::asio::io_context context;
145 using Executor = boost::asio::io_context::executor_type;
146 using Work = boost::asio::executor_work_guard<Executor>;
147 std::optional<Work> work(context.get_executor());
148 std::thread worker([&context] { context.run(); });
149 auto g = make_scope_guard([&work, &worker] {
150 work.reset();
151 worker.join();
152 });
153
154 for (uint64_t i = 0; i < total; i++) {
155 using namespace std::chrono_literals;
156 auto c = throttle.get(obj, wait_for(context, 10ms), 1, 0);
157 outstanding++;
158 outstanding -= c.size();
159 if (max_outstanding < outstanding) {
160 max_outstanding = outstanding;
161 }
162 }
163 auto c = throttle.drain();
164 outstanding -= c.size();
165 EXPECT_EQ(0u, outstanding);
166 EXPECT_EQ(window, max_outstanding);
167 }
168
169 TEST_F(Aio_Throttle, YieldCostOverWindow)
170 {
171 auto obj = make_obj(__PRETTY_FUNCTION__);
172
173 boost::asio::io_context context;
174 spawn::spawn(context,
175 [&] (yield_context yield) {
176 YieldingAioThrottle throttle(4, context, yield);
177 scoped_completion op;
178 auto c = throttle.get(obj, wait_on(op), 8, 0);
179 ASSERT_EQ(1u, c.size());
180 EXPECT_EQ(-EDEADLK, c.front().result);
181 });
182 }
183
184 TEST_F(Aio_Throttle, YieldingThrottleOverMax)
185 {
186 constexpr uint64_t window = 4;
187
188 auto obj = make_obj(__PRETTY_FUNCTION__);
189
190 // issue 32 writes, and verify that max_outstanding <= window
191 constexpr uint64_t total = 32;
192 uint64_t max_outstanding = 0;
193 uint64_t outstanding = 0;
194
195 boost::asio::io_context context;
196 spawn::spawn(context,
197 [&] (yield_context yield) {
198 YieldingAioThrottle throttle(window, context, yield);
199 for (uint64_t i = 0; i < total; i++) {
200 using namespace std::chrono_literals;
201 auto c = throttle.get(obj, wait_for(context, 10ms), 1, 0);
202 outstanding++;
203 outstanding -= c.size();
204 if (max_outstanding < outstanding) {
205 max_outstanding = outstanding;
206 }
207 }
208 auto c = throttle.drain();
209 outstanding -= c.size();
210 });
211 context.poll(); // run until we block
212 EXPECT_EQ(window, outstanding);
213
214 context.run();
215 EXPECT_EQ(0u, outstanding);
216 EXPECT_EQ(window, max_outstanding);
217 }
218
219 } // namespace rgw