1 // Copyright 2021 gRPC authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "src/core/lib/resource_quota/memory_quota.h"
16
17 #include <algorithm>
18 #include <atomic>
19 #include <chrono>
20 #include <random>
21 #include <set>
22 #include <thread>
23 #include <vector>
24
25 #include "gtest/gtest.h"
26
27 #include <grpc/slice.h>
28
29 #include "src/core/lib/iomgr/exec_ctx.h"
30 #include "test/core/resource_quota/call_checker.h"
31 #include "test/core/util/test_config.h"
32
33 namespace grpc_core {
34 namespace testing {
35
36 //
37 // Helpers
38 //
39
40 template <size_t kSize>
41 struct Sized {
42 char blah[kSize];
~Sizedgrpc_core::testing::Sized43 virtual ~Sized() {}
44 };
45
46 //
47 // MemoryRequestTest
48 //
49
TEST(MemoryRequestTest,ConversionFromSize)50 TEST(MemoryRequestTest, ConversionFromSize) {
51 MemoryRequest request = 3;
52 EXPECT_EQ(request.min(), 3);
53 EXPECT_EQ(request.max(), 3);
54 }
55
TEST(MemoryRequestTest,MinMax)56 TEST(MemoryRequestTest, MinMax) {
57 MemoryRequest request(3, 7);
58 EXPECT_EQ(request.min(), 3);
59 EXPECT_EQ(request.max(), 7);
60 }
61
62 //
63 // MemoryQuotaTest
64 //
65
TEST(MemoryQuotaTest,NoOp)66 TEST(MemoryQuotaTest, NoOp) { MemoryQuota("foo"); }
67
TEST(MemoryQuotaTest,CreateAllocatorNoOp)68 TEST(MemoryQuotaTest, CreateAllocatorNoOp) {
69 MemoryQuota memory_quota("foo");
70 auto memory_allocator = memory_quota.CreateMemoryAllocator("bar");
71 }
72
TEST(MemoryQuotaTest,CreateObjectFromAllocator)73 TEST(MemoryQuotaTest, CreateObjectFromAllocator) {
74 ExecCtx exec_ctx;
75 MemoryQuota memory_quota("foo");
76 auto memory_allocator = memory_quota.CreateMemoryAllocator("bar");
77 auto object = memory_allocator.MakeUnique<Sized<4096>>();
78 }
79
TEST(MemoryQuotaTest,CreateSomeObjectsAndExpectReclamation)80 TEST(MemoryQuotaTest, CreateSomeObjectsAndExpectReclamation) {
81 ExecCtx exec_ctx;
82
83 MemoryQuota memory_quota("foo");
84 memory_quota.SetSize(4096);
85 auto memory_allocator = memory_quota.CreateMemoryOwner();
86 auto object = memory_allocator.MakeUnique<Sized<2048>>();
87
88 auto checker1 = CallChecker::Make();
89 memory_allocator.PostReclaimer(
90 ReclamationPass::kDestructive,
91 [&object, checker1](absl::optional<ReclamationSweep> sweep) {
92 checker1->Called();
93 EXPECT_TRUE(sweep.has_value());
94 object.reset();
95 });
96 auto object2 = memory_allocator.MakeUnique<Sized<2048>>();
97 exec_ctx.Flush();
98 EXPECT_EQ(object.get(), nullptr);
99
100 auto checker2 = CallChecker::Make();
101 memory_allocator.PostReclaimer(
102 ReclamationPass::kDestructive,
103 [&object2, checker2](absl::optional<ReclamationSweep> sweep) {
104 checker2->Called();
105 EXPECT_TRUE(sweep.has_value());
106 object2.reset();
107 });
108 auto object3 = memory_allocator.MakeUnique<Sized<2048>>();
109 exec_ctx.Flush();
110 EXPECT_EQ(object2.get(), nullptr);
111 }
112
TEST(MemoryQuotaTest,ReserveRangeNoPressure)113 TEST(MemoryQuotaTest, ReserveRangeNoPressure) {
114 MemoryQuota memory_quota("foo");
115 auto memory_allocator = memory_quota.CreateMemoryAllocator("bar");
116 size_t total = 0;
117 for (int i = 0; i < 10000; i++) {
118 ExecCtx exec_ctx;
119 auto n = memory_allocator.Reserve(MemoryRequest(100, 40000));
120 EXPECT_EQ(n, 40000);
121 total += n;
122 }
123 memory_allocator.Release(total);
124 }
125
TEST(MemoryQuotaTest,MakeSlice)126 TEST(MemoryQuotaTest, MakeSlice) {
127 MemoryQuota memory_quota("foo");
128 auto memory_allocator = memory_quota.CreateMemoryAllocator("bar");
129 std::vector<grpc_slice> slices;
130 for (int i = 1; i < 1000; i++) {
131 ExecCtx exec_ctx;
132 int min = i;
133 int max = 10 * i - 9;
134 slices.push_back(memory_allocator.MakeSlice(MemoryRequest(min, max)));
135 }
136 ExecCtx exec_ctx;
137 for (grpc_slice slice : slices) {
138 grpc_slice_unref(slice);
139 }
140 }
141
TEST(MemoryQuotaTest,ContainerAllocator)142 TEST(MemoryQuotaTest, ContainerAllocator) {
143 ExecCtx exec_ctx;
144 MemoryQuota memory_quota("foo");
145 auto memory_allocator = memory_quota.CreateMemoryAllocator("bar");
146 Vector<int> vec(&memory_allocator);
147 for (int i = 0; i < 100000; i++) {
148 vec.push_back(i);
149 }
150 }
151
TEST(MemoryQuotaTest,NoBunchingIfIdle)152 TEST(MemoryQuotaTest, NoBunchingIfIdle) {
153 // Ensure that we don't queue up useless reclamations even if there are no
154 // memory reclamations needed.
155 MemoryQuota memory_quota("foo");
156 std::atomic<size_t> count_reclaimers_called{0};
157
158 for (size_t i = 0; i < 10000; i++) {
159 ExecCtx exec_ctx;
160 auto memory_owner = memory_quota.CreateMemoryOwner();
161 memory_owner.PostReclaimer(
162 ReclamationPass::kDestructive,
163 [&count_reclaimers_called](absl::optional<ReclamationSweep> sweep) {
164 EXPECT_FALSE(sweep.has_value());
165 count_reclaimers_called.fetch_add(1, std::memory_order_relaxed);
166 });
167 auto object = memory_owner.MakeUnique<Sized<2048>>();
168 }
169
170 EXPECT_GE(count_reclaimers_called.load(std::memory_order_relaxed), 8000);
171 }
172
TEST(MemoryQuotaTest,AllMemoryQuotas)173 TEST(MemoryQuotaTest, AllMemoryQuotas) {
174 auto gather = []() {
175 std::set<std::string> all_names;
176 for (const auto& q : AllMemoryQuotas()) {
177 all_names.emplace(q->name());
178 }
179 return all_names;
180 };
181
182 auto m1 = MakeMemoryQuota("m1");
183 auto m2 = MakeMemoryQuota("m2");
184
185 EXPECT_EQ(gather(), std::set<std::string>({"m1", "m2"}));
186 m1.reset();
187 EXPECT_EQ(gather(), std::set<std::string>({"m2"}));
188 }
189
190 } // namespace testing
191
192 namespace memory_quota_detail {
193 namespace testing {
194
195 //
196 // PressureControllerTest
197 //
198
TEST(PressureControllerTest,Init)199 TEST(PressureControllerTest, Init) {
200 PressureController c{100, 3};
201 EXPECT_EQ(c.Update(-1.0), 0.0);
202 EXPECT_EQ(c.Update(1.0), 1.0);
203 }
204
TEST(PressureControllerTest,LowDecays)205 TEST(PressureControllerTest, LowDecays) {
206 PressureController c{100, 3};
207 EXPECT_EQ(c.Update(1.0), 1.0);
208 double last = 1.0;
209 while (last > 1e-30) {
210 double x = c.Update(-1.0);
211 EXPECT_LE(x, last);
212 last = x;
213 }
214 }
215
216 //
217 // PressureTrackerTest
218 //
219
TEST(PressureTrackerTest,NoOp)220 TEST(PressureTrackerTest, NoOp) { PressureTracker(); }
221
TEST(PressureTrackerTest,Decays)222 TEST(PressureTrackerTest, Decays) {
223 PressureTracker tracker;
224 int cur_ms = 0;
225 auto step_time = [&] {
226 ++cur_ms;
227 return Timestamp::ProcessEpoch() + Duration::Seconds(1) +
228 Duration::Milliseconds(cur_ms);
229 };
230 // At start pressure is zero and we should be reading zero back.
231 {
232 ExecCtx exec_ctx;
233 exec_ctx.TestOnlySetNow(step_time());
234 EXPECT_EQ(tracker.AddSampleAndGetControlValue(0.0), 0.0);
235 }
236 // If memory pressure goes to 100% or higher, we should *immediately* snap to
237 // reporting 100%.
238 {
239 ExecCtx exec_ctx;
240 exec_ctx.TestOnlySetNow(step_time());
241 EXPECT_EQ(tracker.AddSampleAndGetControlValue(1.0), 1.0);
242 }
243 // Once memory pressure reduces, we should *eventually* get back to reporting
244 // close to zero, and monotonically decrease.
245 const int got_full = cur_ms;
246 double last_reported = 1.0;
247 while (true) {
248 ExecCtx exec_ctx;
249 exec_ctx.TestOnlySetNow(step_time());
250 double new_reported = tracker.AddSampleAndGetControlValue(0.0);
251 EXPECT_LE(new_reported, last_reported);
252 last_reported = new_reported;
253 if (new_reported < 0.1) break;
254 }
255 // Verify the above happened in a somewhat reasonable time.
256 ASSERT_LE(cur_ms, got_full + 1000000);
257 }
258
TEST(PressureTrackerTest,ManyThreads)259 TEST(PressureTrackerTest, ManyThreads) {
260 PressureTracker tracker;
261 std::vector<std::thread> threads;
262 std::atomic<bool> shutdown{false};
263 threads.reserve(10);
264 for (int i = 0; i < 10; i++) {
265 threads.emplace_back([&tracker, &shutdown] {
266 std::random_device rng;
267 std::uniform_real_distribution<double> dist(0.0, 1.0);
268 while (!shutdown.load(std::memory_order_relaxed)) {
269 ExecCtx exec_ctx;
270 tracker.AddSampleAndGetControlValue(dist(rng));
271 }
272 });
273 }
274 std::this_thread::sleep_for(std::chrono::seconds(5));
275 shutdown.store(true, std::memory_order_relaxed);
276 for (auto& thread : threads) {
277 thread.join();
278 }
279 }
280
281 } // namespace testing
282 } // namespace memory_quota_detail
283
284 } // namespace grpc_core
285
main(int argc,char ** argv)286 int main(int argc, char** argv) {
287 grpc::testing::TestEnvironment give_me_a_name(&argc, argv);
288 ::testing::InitGoogleTest(&argc, argv);
289 gpr_log_verbosity_init();
290 return RUN_ALL_TESTS();
291 }
292