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 <grpc/slice.h>
18 #include <grpc/support/log.h>
19
20 #include <algorithm>
21 #include <atomic>
22 #include <chrono>
23 #include <random>
24 #include <set>
25 #include <thread>
26 #include <vector>
27
28 #include "gtest/gtest.h"
29 #include "src/core/lib/iomgr/exec_ctx.h"
30 #include "test/core/resource_quota/call_checker.h"
31 #include "test/core/test_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
TEST(MemoryQuotaTest,ContainerMemoryAccountedFor)190 TEST(MemoryQuotaTest, ContainerMemoryAccountedFor) {
191 MemoryQuota memory_quota("foo");
192 memory_quota.SetSize(1000000);
193 EXPECT_EQ(ContainerMemoryPressure(), 0.0);
194 auto owner = memory_quota.CreateMemoryOwner();
195 const double original_memory_pressure =
196 owner.GetPressureInfo().instantaneous_pressure;
197 EXPECT_LT(original_memory_pressure, 0.01);
198 SetContainerMemoryPressure(1.0);
199 EXPECT_EQ(owner.GetPressureInfo().instantaneous_pressure, 1.0);
200 SetContainerMemoryPressure(0.0);
201 EXPECT_EQ(owner.GetPressureInfo().instantaneous_pressure,
202 original_memory_pressure);
203 SetContainerMemoryPressure(0.0);
204 }
205
206 } // namespace testing
207
208 namespace memory_quota_detail {
209 namespace testing {
210
211 //
212 // PressureControllerTest
213 //
214
TEST(PressureControllerTest,Init)215 TEST(PressureControllerTest, Init) {
216 PressureController c{100, 3};
217 EXPECT_EQ(c.Update(-1.0), 0.0);
218 EXPECT_EQ(c.Update(1.0), 1.0);
219 }
220
TEST(PressureControllerTest,LowDecays)221 TEST(PressureControllerTest, LowDecays) {
222 PressureController c{100, 3};
223 EXPECT_EQ(c.Update(1.0), 1.0);
224 double last = 1.0;
225 while (last > 1e-30) {
226 double x = c.Update(-1.0);
227 EXPECT_LE(x, last);
228 last = x;
229 }
230 }
231
232 //
233 // PressureTrackerTest
234 //
235
TEST(PressureTrackerTest,NoOp)236 TEST(PressureTrackerTest, NoOp) { PressureTracker(); }
237
TEST(PressureTrackerTest,Decays)238 TEST(PressureTrackerTest, Decays) {
239 PressureTracker tracker;
240 int cur_ms = 0;
241 auto step_time = [&] {
242 ++cur_ms;
243 return Timestamp::ProcessEpoch() + Duration::Seconds(1) +
244 Duration::Milliseconds(cur_ms);
245 };
246 // At start pressure is zero and we should be reading zero back.
247 {
248 ExecCtx exec_ctx;
249 exec_ctx.TestOnlySetNow(step_time());
250 EXPECT_EQ(tracker.AddSampleAndGetControlValue(0.0), 0.0);
251 }
252 // If memory pressure goes to 100% or higher, we should *immediately* snap to
253 // reporting 100%.
254 {
255 ExecCtx exec_ctx;
256 exec_ctx.TestOnlySetNow(step_time());
257 EXPECT_EQ(tracker.AddSampleAndGetControlValue(1.0), 1.0);
258 }
259 // Once memory pressure reduces, we should *eventually* get back to reporting
260 // close to zero, and monotonically decrease.
261 const int got_full = cur_ms;
262 double last_reported = 1.0;
263 while (true) {
264 ExecCtx exec_ctx;
265 exec_ctx.TestOnlySetNow(step_time());
266 double new_reported = tracker.AddSampleAndGetControlValue(0.0);
267 EXPECT_LE(new_reported, last_reported);
268 last_reported = new_reported;
269 if (new_reported < 0.1) break;
270 }
271 // Verify the above happened in a somewhat reasonable time.
272 ASSERT_LE(cur_ms, got_full + 1000000);
273 }
274
TEST(PressureTrackerTest,ManyThreads)275 TEST(PressureTrackerTest, ManyThreads) {
276 PressureTracker tracker;
277 std::vector<std::thread> threads;
278 std::atomic<bool> shutdown{false};
279 threads.reserve(10);
280 for (int i = 0; i < 10; i++) {
281 threads.emplace_back([&tracker, &shutdown] {
282 std::random_device rng;
283 std::uniform_real_distribution<double> dist(0.0, 1.0);
284 while (!shutdown.load(std::memory_order_relaxed)) {
285 ExecCtx exec_ctx;
286 tracker.AddSampleAndGetControlValue(dist(rng));
287 }
288 });
289 }
290 std::this_thread::sleep_for(std::chrono::seconds(5));
291 shutdown.store(true, std::memory_order_relaxed);
292 for (auto& thread : threads) {
293 thread.join();
294 }
295 }
296
297 } // namespace testing
298 } // namespace memory_quota_detail
299
300 } // namespace grpc_core
301
main(int argc,char ** argv)302 int main(int argc, char** argv) {
303 grpc::testing::TestEnvironment give_me_a_name(&argc, argv);
304 ::testing::InitGoogleTest(&argc, argv);
305 gpr_log_verbosity_init();
306 return RUN_ALL_TESTS();
307 }
308