]>
git.proxmox.com Git - ceph.git/blob - ceph/src/test/common/Throttle.cc
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
4 * Ceph - scalable distributed file system
6 * Copyright (C) 2013 Cloudwatt <libre.licensing@cloudwatt.com>
8 * Author: Loic Dachary <loic@dachary.org>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU Library Public License as published by
12 * the Free Software Foundation; either version 2, or (at your option)
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Library Public License for more details.
24 #include "gtest/gtest.h"
25 #include "common/Mutex.h"
26 #include "common/Thread.h"
27 #include "common/Throttle.h"
28 #include "common/ceph_argparse.h"
37 class ThrottleTest
: public ::testing::Test
{
40 class Thread_get
: public Thread
{
46 Thread_get(Throttle
& _throttle
, int64_t _count
) :
53 void *entry() override
{
55 waited
= throttle
.get(count
);
63 TEST_F(ThrottleTest
, Throttle
) {
64 int64_t throttle_max
= 10;
65 Throttle
throttle(g_ceph_context
, "throttle", throttle_max
);
66 ASSERT_EQ(throttle
.get_max(), throttle_max
);
67 ASSERT_EQ(throttle
.get_current(), 0);
70 TEST_F(ThrottleTest
, take
) {
71 int64_t throttle_max
= 10;
72 Throttle
throttle(g_ceph_context
, "throttle", throttle_max
);
73 ASSERT_EQ(throttle
.take(throttle_max
), throttle_max
);
74 ASSERT_EQ(throttle
.take(throttle_max
), throttle_max
* 2);
77 TEST_F(ThrottleTest
, get
) {
78 int64_t throttle_max
= 10;
79 Throttle
throttle(g_ceph_context
, "throttle");
81 // test increasing max from 0 to throttle_max
83 ASSERT_FALSE(throttle
.get(throttle_max
, throttle_max
));
84 ASSERT_EQ(throttle
.get_max(), throttle_max
);
85 ASSERT_EQ(throttle
.put(throttle_max
), 0);
88 ASSERT_FALSE(throttle
.get(5));
89 ASSERT_EQ(throttle
.put(5), 0);
91 ASSERT_FALSE(throttle
.get(throttle_max
));
92 ASSERT_FALSE(throttle
.get_or_fail(1));
93 ASSERT_FALSE(throttle
.get(1, throttle_max
+ 1));
94 ASSERT_EQ(throttle
.put(throttle_max
+ 1), 0);
95 ASSERT_FALSE(throttle
.get(0, throttle_max
));
96 ASSERT_FALSE(throttle
.get(throttle_max
));
97 ASSERT_FALSE(throttle
.get_or_fail(1));
98 ASSERT_EQ(throttle
.put(throttle_max
), 0);
100 useconds_t delay
= 1;
105 cout
<< "Trying (1) with delay " << delay
<< "us\n";
107 ASSERT_FALSE(throttle
.get(throttle_max
));
108 ASSERT_FALSE(throttle
.get_or_fail(throttle_max
));
110 Thread_get
t(throttle
, 7);
111 t
.create("t_throttle_1");
113 ASSERT_EQ(throttle
.put(throttle_max
), 0);
116 if (!(waited
= t
.waited
))
122 cout
<< "Trying (2) with delay " << delay
<< "us\n";
124 ASSERT_FALSE(throttle
.get(throttle_max
/ 2));
125 ASSERT_FALSE(throttle
.get_or_fail(throttle_max
));
127 Thread_get
t(throttle
, throttle_max
);
128 t
.create("t_throttle_2");
131 Thread_get
u(throttle
, 1);
132 u
.create("u_throttle_2");
135 throttle
.put(throttle_max
/ 2);
140 if (!(waited
= t
.waited
&& u
.waited
))
146 TEST_F(ThrottleTest
, get_or_fail
) {
148 Throttle
throttle(g_ceph_context
, "throttle");
150 ASSERT_TRUE(throttle
.get_or_fail(5));
151 ASSERT_TRUE(throttle
.get_or_fail(5));
155 int64_t throttle_max
= 10;
156 Throttle
throttle(g_ceph_context
, "throttle", throttle_max
);
158 ASSERT_TRUE(throttle
.get_or_fail(throttle_max
));
159 ASSERT_EQ(throttle
.put(throttle_max
), 0);
161 ASSERT_TRUE(throttle
.get_or_fail(throttle_max
* 2));
162 ASSERT_FALSE(throttle
.get_or_fail(1));
163 ASSERT_FALSE(throttle
.get_or_fail(throttle_max
* 2));
164 ASSERT_EQ(throttle
.put(throttle_max
* 2), 0);
166 ASSERT_TRUE(throttle
.get_or_fail(throttle_max
));
167 ASSERT_FALSE(throttle
.get_or_fail(1));
168 ASSERT_EQ(throttle
.put(throttle_max
), 0);
172 TEST_F(ThrottleTest
, wait
) {
173 int64_t throttle_max
= 10;
174 Throttle
throttle(g_ceph_context
, "throttle");
176 // test increasing max from 0 to throttle_max
178 ASSERT_FALSE(throttle
.wait(throttle_max
));
179 ASSERT_EQ(throttle
.get_max(), throttle_max
);
182 useconds_t delay
= 1;
187 cout
<< "Trying (3) with delay " << delay
<< "us\n";
189 ASSERT_FALSE(throttle
.get(throttle_max
/ 2));
190 ASSERT_FALSE(throttle
.get_or_fail(throttle_max
));
192 Thread_get
t(throttle
, throttle_max
);
193 t
.create("t_throttle_3");
197 // Throttle::_reset_max(int64_t m) used to contain a test
198 // that blocked the following statement, only if
199 // the argument was greater than throttle_max.
200 // Although a value lower than throttle_max would cover
201 // the same code in _reset_max, the throttle_max * 100
202 // value is left here to demonstrate that the problem
205 throttle
.wait(throttle_max
* 100);
208 ASSERT_EQ(throttle
.get_current(), throttle_max
/ 2);
210 if (!(waited
= t
.waited
)) {
212 // undo the changes we made
213 throttle
.put(throttle_max
/ 2);
214 throttle
.wait(throttle_max
);
219 TEST_F(ThrottleTest
, destructor
) {
222 int64_t throttle_max
= 10;
223 Throttle
*throttle
= new Throttle(g_ceph_context
, "throttle", throttle_max
);
225 ASSERT_FALSE(throttle
->get(5));
227 t
= new Thread_get(*throttle
, 7);
228 t
->create("t_throttle");
230 useconds_t delay
= 1;
233 if (throttle
->get_or_fail(1)) {
245 // The thread is left hanging, otherwise it will abort().
246 // Deleting the Throttle on which it is waiting creates a
247 // inconsistency that will be detected: the Throttle object that
248 // it references no longer exists.
250 pthread_t id
= t
->get_thread_id();
251 ASSERT_EQ(pthread_kill(id
, 0), 0);
253 ASSERT_EQ(pthread_kill(id
, 0), 0);
257 std::pair
<double, std::chrono::duration
<double> > test_backoff(
258 double low_threshhold
,
259 double high_threshhold
,
260 double expected_throughput
,
261 double high_multiple
,
264 double put_delay_per_count
,
269 std::condition_variable c
;
271 std::list
<uint64_t> in_queue
;
272 bool stop_getters
= false;
273 bool stop_putters
= false;
275 auto wait_time
= std::chrono::duration
<double>(0);
278 uint64_t total_observed_total
= 0;
279 uint64_t total_observations
= 0;
281 BackoffThrottle
throttle(g_ceph_context
, "backoff_throttle_test", 5);
282 bool valid
= throttle
.set_params(
292 auto getter
= [&]() {
293 std::random_device rd
;
294 std::mt19937
gen(rd());
295 std::uniform_int_distribution
<> dis(0, 10);
297 std::unique_lock
<std::mutex
> g(l
);
298 while (!stop_getters
) {
301 uint64_t to_get
= dis(gen
);
302 auto waited
= throttle
.get(to_get
);
308 in_queue
.push_back(to_get
);
313 auto putter
= [&]() {
314 std::unique_lock
<std::mutex
> g(l
);
315 while (!stop_putters
|| !in_queue
.empty()) {
316 if (in_queue
.empty()) {
321 uint64_t c
= in_queue
.front();
323 total_observed_total
+= total
;
324 total_observations
++;
325 in_queue
.pop_front();
326 assert(total
<= max
);
329 std::this_thread::sleep_for(
330 c
* std::chrono::duration
<double>(put_delay_per_count
*putters
));
338 vector
<std::thread
> gts(getters
);
339 for (auto &&i
: gts
) i
= std::thread(getter
);
341 vector
<std::thread
> pts(putters
);
342 for (auto &&i
: pts
) i
= std::thread(putter
);
344 std::this_thread::sleep_for(std::chrono::duration
<double>(5));
346 std::unique_lock
<std::mutex
> g(l
);
350 for (auto &&i
: gts
) i
.join();
354 std::unique_lock
<std::mutex
> g(l
);
358 for (auto &&i
: pts
) i
.join();
362 ((double)total_observed_total
)/((double)total_observations
),
366 TEST(BackoffThrottle
, undersaturated
)
368 auto results
= test_backoff(
378 ASSERT_LT(results
.first
, 45);
379 ASSERT_GT(results
.first
, 35);
380 ASSERT_LT(results
.second
.count(), 0.0002);
381 ASSERT_GT(results
.second
.count(), 0.00005);
384 TEST(BackoffThrottle
, balanced
)
386 auto results
= test_backoff(
396 ASSERT_LT(results
.first
, 60);
397 ASSERT_GT(results
.first
, 40);
398 ASSERT_LT(results
.second
.count(), 0.002);
399 ASSERT_GT(results
.second
.count(), 0.0005);
402 TEST(BackoffThrottle
, oversaturated
)
404 auto results
= test_backoff(
414 ASSERT_LT(results
.first
, 101);
415 ASSERT_GT(results
.first
, 85);
416 ASSERT_LT(results
.second
.count(), 0.002);
417 ASSERT_GT(results
.second
.count(), 0.0005);
422 * compile-command: "cd ../.. ;
423 * make unittest_throttle ;
424 * ./unittest_throttle # --gtest_filter=ThrottleTest.destructor \
425 * --log-to-stderr=true --debug-filestore=20