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"
29 #include "common/backport14.h"
38 class ThrottleTest : public ::testing::Test {
41 class Thread_get : public Thread {
47 Thread_get(Throttle& _throttle, int64_t _count) :
54 void *entry() override {
56 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);
220 TEST_F(ThrottleTest, destructor) {
222 int64_t throttle_max = 10;
223 auto throttle = ceph::make_unique<Throttle>(g_ceph_context, "throttle",
227 ASSERT_FALSE(throttle->get(5));
228 unique_ptr<Thread_get> t = ceph::make_unique<Thread_get>(*throttle, 7);
229 t->create("t_throttle");
231 useconds_t delay = 1;
234 if (throttle->get_or_fail(1)) {
245 std::pair<double, std::chrono::duration<double> > test_backoff(
246 double low_threshhold,
247 double high_threshhold,
248 double expected_throughput,
249 double high_multiple,
252 double put_delay_per_count,
257 std::condition_variable c;
259 std::list<uint64_t> in_queue;
260 bool stop_getters = false;
261 bool stop_putters = false;
263 auto wait_time = std::chrono::duration<double>(0);
266 uint64_t total_observed_total = 0;
267 uint64_t total_observations = 0;
269 BackoffThrottle throttle(g_ceph_context, "backoff_throttle_test", 5);
270 bool valid = throttle.set_params(
280 auto getter = [&]() {
281 std::random_device rd;
282 std::mt19937 gen(rd());
283 std::uniform_int_distribution<> dis(0, 10);
285 std::unique_lock<std::mutex> g(l);
286 while (!stop_getters) {
289 uint64_t to_get = dis(gen);
290 auto waited = throttle.get(to_get);
296 in_queue.push_back(to_get);
301 auto putter = [&]() {
302 std::unique_lock<std::mutex> g(l);
303 while (!stop_putters || !in_queue.empty()) {
304 if (in_queue.empty()) {
309 uint64_t c = in_queue.front();
311 total_observed_total += total;
312 total_observations++;
313 in_queue.pop_front();
314 assert(total <= max);
317 std::this_thread::sleep_for(
318 c * std::chrono::duration<double>(put_delay_per_count*putters));
326 vector<std::thread> gts(getters);
327 for (auto &&i: gts) i = std::thread(getter);
329 vector<std::thread> pts(putters);
330 for (auto &&i: pts) i = std::thread(putter);
332 std::this_thread::sleep_for(std::chrono::duration<double>(5));
334 std::unique_lock<std::mutex> g(l);
338 for (auto &&i: gts) i.join();
342 std::unique_lock<std::mutex> g(l);
346 for (auto &&i: pts) i.join();
350 ((double)total_observed_total)/((double)total_observations),
354 TEST(BackoffThrottle, destruct) {
356 auto throttle = ceph::make_unique<BackoffThrottle>(
357 g_ceph_context, "destructor test", 10);
358 ASSERT_TRUE(throttle->set_params(0.4, 0.6, 1000, 2, 10, 6, nullptr));
368 // No equivalent of get_or_fail()
369 std::this_thread::sleep_for(std::chrono::milliseconds(250));
373 TEST(BackoffThrottle, undersaturated)
375 auto results = test_backoff(
385 ASSERT_LT(results.first, 45);
386 ASSERT_GT(results.first, 35);
387 ASSERT_LT(results.second.count(), 0.0002);
388 ASSERT_GT(results.second.count(), 0.00005);
391 TEST(BackoffThrottle, balanced)
393 auto results = test_backoff(
403 ASSERT_LT(results.first, 60);
404 ASSERT_GT(results.first, 40);
405 ASSERT_LT(results.second.count(), 0.002);
406 ASSERT_GT(results.second.count(), 0.0005);
409 TEST(BackoffThrottle, oversaturated)
411 auto results = test_backoff(
421 ASSERT_LT(results.first, 101);
422 ASSERT_GT(results.first, 85);
423 ASSERT_LT(results.second.count(), 0.002);
424 ASSERT_GT(results.second.count(), 0.0005);
427 TEST(OrderedThrottle, destruct) {
429 auto throttle = ceph::make_unique<OrderedThrottle>(1, false);
430 throttle->start_op(nullptr);
438 // No equivalent of get_or_fail()
439 std::this_thread::sleep_for(std::chrono::milliseconds(250));
445 * compile-command: "cd ../.. ;
446 * make unittest_throttle ;
447 * ./unittest_throttle # --gtest_filter=ThrottleTest.destructor \
448 * --log-to-stderr=true --debug-filestore=20