/Users/deen/code/yugabyte-db/src/yb/util/mem_tracker-test.cc
Line | Count | Source |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | // |
18 | | // The following only applies to changes made to this file as part of YugaByte development. |
19 | | // |
20 | | // Portions Copyright (c) YugaByte, Inc. |
21 | | // |
22 | | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
23 | | // in compliance with the License. You may obtain a copy of the License at |
24 | | // |
25 | | // http://www.apache.org/licenses/LICENSE-2.0 |
26 | | // |
27 | | // Unless required by applicable law or agreed to in writing, software distributed under the License |
28 | | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
29 | | // or implied. See the License for the specific language governing permissions and limitations |
30 | | // under the License. |
31 | | // |
32 | | |
33 | | #include "yb/util/mem_tracker.h" |
34 | | |
35 | | #include <string> |
36 | | #include <unordered_map> |
37 | | #include <utility> |
38 | | #include <vector> |
39 | | |
40 | | #ifdef TCMALLOC_ENABLED |
41 | | #include <gperftools/malloc_extension.h> |
42 | | #endif |
43 | | |
44 | | #include "yb/util/result.h" |
45 | | #include "yb/util/size_literals.h" |
46 | | #include "yb/util/test_macros.h" |
47 | | #include "yb/util/test_util.h" |
48 | | |
49 | | DECLARE_int32(memory_limit_soft_percentage); |
50 | | DECLARE_int64(mem_tracker_update_consumption_interval_us); |
51 | | DECLARE_int64(mem_tracker_tcmalloc_gc_release_bytes); |
52 | | |
53 | | namespace yb { |
54 | | |
55 | | using std::equal_to; |
56 | | using std::hash; |
57 | | using std::pair; |
58 | | using std::shared_ptr; |
59 | | using std::string; |
60 | | using std::unordered_map; |
61 | | using std::vector; |
62 | | |
63 | 1 | TEST(MemTrackerTest, SingleTrackerNoLimit) { |
64 | 1 | shared_ptr<MemTracker> t = MemTracker::CreateTracker("t"); |
65 | 1 | EXPECT_FALSE(t->has_limit()); |
66 | 1 | t->Consume(10); |
67 | 1 | EXPECT_EQ(t->consumption(), 10); |
68 | 1 | t->Consume(10); |
69 | 1 | EXPECT_EQ(t->consumption(), 20); |
70 | 1 | t->Release(15); |
71 | 1 | EXPECT_EQ(t->consumption(), 5); |
72 | 1 | EXPECT_FALSE(t->LimitExceeded()); |
73 | 1 | t->Release(5); |
74 | 1 | EXPECT_EQ(t->consumption(), 0); |
75 | 1 | } |
76 | | |
77 | 1 | TEST(MemTrackerTest, SingleTrackerWithLimit) { |
78 | 1 | shared_ptr<MemTracker> t = MemTracker::CreateTracker(11, "t"); |
79 | 1 | EXPECT_TRUE(t->has_limit()); |
80 | 1 | t->Consume(10); |
81 | 1 | EXPECT_EQ(t->consumption(), 10); |
82 | 1 | EXPECT_FALSE(t->LimitExceeded()); |
83 | 1 | t->Consume(10); |
84 | 1 | EXPECT_EQ(t->consumption(), 20); |
85 | 1 | EXPECT_TRUE(t->LimitExceeded()); |
86 | 1 | t->Release(15); |
87 | 1 | EXPECT_EQ(t->consumption(), 5); |
88 | 1 | EXPECT_FALSE(t->LimitExceeded()); |
89 | 1 | t->Release(5); |
90 | 1 | } |
91 | | |
92 | 1 | TEST(MemTrackerTest, TrackerHierarchy) { |
93 | 1 | shared_ptr<MemTracker> p = MemTracker::CreateTracker(100, "p"); |
94 | 1 | shared_ptr<MemTracker> c1 = MemTracker::CreateTracker(80, "c1", p); |
95 | 1 | shared_ptr<MemTracker> c2 = MemTracker::CreateTracker(50, "c2", p); |
96 | | |
97 | | // everything below limits |
98 | 1 | c1->Consume(60); |
99 | 1 | EXPECT_EQ(c1->consumption(), 60); |
100 | 1 | EXPECT_FALSE(c1->LimitExceeded()); |
101 | 1 | EXPECT_FALSE(c1->AnyLimitExceeded()); |
102 | 1 | EXPECT_EQ(c2->consumption(), 0); |
103 | 1 | EXPECT_FALSE(c2->LimitExceeded()); |
104 | 1 | EXPECT_FALSE(c2->AnyLimitExceeded()); |
105 | 1 | EXPECT_EQ(p->consumption(), 60); |
106 | 1 | EXPECT_FALSE(p->LimitExceeded()); |
107 | 1 | EXPECT_FALSE(p->AnyLimitExceeded()); |
108 | | |
109 | | // p goes over limit |
110 | 1 | c2->Consume(50); |
111 | 1 | EXPECT_EQ(c1->consumption(), 60); |
112 | 1 | EXPECT_FALSE(c1->LimitExceeded()); |
113 | 1 | EXPECT_TRUE(c1->AnyLimitExceeded()); |
114 | 1 | EXPECT_EQ(c2->consumption(), 50); |
115 | 1 | EXPECT_FALSE(c2->LimitExceeded()); |
116 | 1 | EXPECT_TRUE(c2->AnyLimitExceeded()); |
117 | 1 | EXPECT_EQ(p->consumption(), 110); |
118 | 1 | EXPECT_TRUE(p->LimitExceeded()); |
119 | | |
120 | | // c2 goes over limit, p drops below limit |
121 | 1 | c1->Release(20); |
122 | 1 | c2->Consume(10); |
123 | 1 | EXPECT_EQ(c1->consumption(), 40); |
124 | 1 | EXPECT_FALSE(c1->LimitExceeded()); |
125 | 1 | EXPECT_FALSE(c1->AnyLimitExceeded()); |
126 | 1 | EXPECT_EQ(c2->consumption(), 60); |
127 | 1 | EXPECT_TRUE(c2->LimitExceeded()); |
128 | 1 | EXPECT_TRUE(c2->AnyLimitExceeded()); |
129 | 1 | EXPECT_EQ(p->consumption(), 100); |
130 | 1 | EXPECT_FALSE(p->LimitExceeded()); |
131 | 1 | c1->Release(40); |
132 | 1 | c2->Release(60); |
133 | 1 | } |
134 | | |
135 | | namespace { |
136 | | |
137 | | class GcTest : public GarbageCollector { |
138 | | public: |
139 | | static const int NUM_RELEASE_BYTES = 1; |
140 | | |
141 | 3 | explicit GcTest(MemTracker* tracker) : tracker_(tracker) {} |
142 | | |
143 | 4 | void CollectGarbage(size_t required) { tracker_->Release(NUM_RELEASE_BYTES); } |
144 | | |
145 | 3 | virtual ~GcTest() {} |
146 | | |
147 | | private: |
148 | | MemTracker* tracker_; |
149 | | }; |
150 | | |
151 | | } // namespace |
152 | | |
153 | 1 | TEST(MemTrackerTest, GcFunctions) { |
154 | 1 | shared_ptr<MemTracker> t = MemTracker::CreateTracker(10, ""); |
155 | 1 | ASSERT_TRUE(t->has_limit()); |
156 | | |
157 | 1 | t->Consume(9); |
158 | 1 | EXPECT_FALSE(t->LimitExceeded()); |
159 | | |
160 | | // Test TryConsume() |
161 | 1 | EXPECT_FALSE(t->TryConsume(2)); |
162 | 1 | EXPECT_EQ(t->consumption(), 9); |
163 | 1 | EXPECT_FALSE(t->LimitExceeded()); |
164 | | |
165 | | // Attach GcFunction that releases 1 byte |
166 | 1 | auto gc = std::make_shared<GcTest>(t.get()); |
167 | 1 | t->AddGarbageCollector(gc); |
168 | 1 | EXPECT_TRUE(t->TryConsume(2)); |
169 | 1 | EXPECT_EQ(t->consumption(), 10); |
170 | 1 | EXPECT_FALSE(t->LimitExceeded()); |
171 | | |
172 | | // GcFunction will be called even though TryConsume() fails |
173 | 1 | EXPECT_FALSE(t->TryConsume(2)); |
174 | 1 | EXPECT_EQ(t->consumption(), 9); |
175 | 1 | EXPECT_FALSE(t->LimitExceeded()); |
176 | | |
177 | | // GcFunction won't be called |
178 | 1 | EXPECT_TRUE(t->TryConsume(1)); |
179 | 1 | EXPECT_EQ(t->consumption(), 10); |
180 | 1 | EXPECT_FALSE(t->LimitExceeded()); |
181 | | |
182 | | // Test LimitExceeded() |
183 | 1 | t->Consume(1); |
184 | 1 | EXPECT_EQ(t->consumption(), 11); |
185 | 1 | EXPECT_FALSE(t->LimitExceeded()); |
186 | 1 | EXPECT_EQ(t->consumption(), 10); |
187 | | |
188 | | // Add more GcFunctions, test that we only call them until the limit is no longer |
189 | | // exceeded |
190 | 1 | auto gc2 = std::make_shared<GcTest>(t.get()); |
191 | 1 | t->AddGarbageCollector(gc2); |
192 | 1 | auto gc3 = std::make_shared<GcTest>(t.get()); |
193 | 1 | t->AddGarbageCollector(gc3); |
194 | 1 | t->Consume(1); |
195 | 1 | EXPECT_EQ(t->consumption(), 11); |
196 | 1 | EXPECT_FALSE(t->LimitExceeded()); |
197 | 1 | EXPECT_EQ(t->consumption(), 10); |
198 | 1 | t->Release(10); |
199 | 1 | } |
200 | | |
201 | 1 | TEST(MemTrackerTest, STLContainerAllocator) { |
202 | 1 | shared_ptr<MemTracker> t = MemTracker::CreateTracker("t"); |
203 | 1 | MemTrackerAllocator<int> vec_alloc(t); |
204 | 1 | MemTrackerAllocator<pair<const int, int>> map_alloc(t); |
205 | | |
206 | | // Simple test: use the allocator in a vector. |
207 | 1 | { |
208 | 1 | vector<int, MemTrackerAllocator<int> > v(vec_alloc); |
209 | 1 | ASSERT_EQ(0, t->consumption()); |
210 | 1 | v.reserve(5); |
211 | 1 | ASSERT_EQ(5 * sizeof(int), t->consumption()); |
212 | 1 | v.reserve(10); |
213 | 1 | ASSERT_EQ(10 * sizeof(int), t->consumption()); |
214 | 1 | } |
215 | 1 | ASSERT_EQ(0, t->consumption()); |
216 | | |
217 | | // Complex test: use it in an unordered_map, where it must be rebound in |
218 | | // order to allocate the map's buckets. |
219 | 1 | { |
220 | 1 | unordered_map<int, int, hash<int>, equal_to<int>, MemTrackerAllocator<pair<const int, int>>> um( |
221 | 1 | 10, |
222 | 1 | hash<int>(), |
223 | 1 | equal_to<int>(), |
224 | 1 | map_alloc); |
225 | | |
226 | | // Don't care about the value (it depends on map internals). |
227 | 1 | ASSERT_GT(t->consumption(), 0); |
228 | 1 | } |
229 | 1 | ASSERT_EQ(0, t->consumption()); |
230 | 1 | } |
231 | | |
232 | 1 | TEST(MemTrackerTest, FindFunctionsTakeOwnership) { |
233 | | // In each test, ToString() would crash if the MemTracker is destroyed when |
234 | | // 'm' goes out of scope. |
235 | | |
236 | 1 | shared_ptr<MemTracker> ref; |
237 | 1 | { |
238 | 1 | shared_ptr<MemTracker> m = MemTracker::CreateTracker("test"); |
239 | 1 | ref = MemTracker::FindTracker(m->id()); |
240 | 1 | ASSERT_TRUE(ref != nullptr); |
241 | 1 | } |
242 | 1 | LOG(INFO) << ref->ToString(); |
243 | 1 | ref.reset(); |
244 | | |
245 | 1 | { |
246 | 1 | shared_ptr<MemTracker> m = MemTracker::CreateTracker("test"); |
247 | 1 | ref = MemTracker::FindOrCreateTracker(m->id()); |
248 | 1 | } |
249 | 1 | LOG(INFO) << ref->ToString(); |
250 | 1 | ref.reset(); |
251 | | |
252 | 1 | vector<shared_ptr<MemTracker> > refs; |
253 | 1 | { |
254 | 1 | shared_ptr<MemTracker> m = MemTracker::CreateTracker("test"); |
255 | 1 | refs = MemTracker::ListTrackers(); |
256 | 1 | } |
257 | 2 | for (const shared_ptr<MemTracker>& r : refs) { |
258 | 2 | LOG(INFO) << r->ToString(); |
259 | 2 | } |
260 | 1 | refs.clear(); |
261 | 1 | } |
262 | | |
263 | 1 | TEST(MemTrackerTest, ScopedTrackedConsumption) { |
264 | 1 | shared_ptr<MemTracker> m = MemTracker::CreateTracker("test"); |
265 | 1 | ASSERT_EQ(0, m->consumption()); |
266 | 1 | { |
267 | 1 | ScopedTrackedConsumption consumption(m, 1); |
268 | 1 | ASSERT_EQ(1, m->consumption()); |
269 | | |
270 | 1 | consumption.Reset(3); |
271 | 1 | ASSERT_EQ(3, m->consumption()); |
272 | 1 | } |
273 | 1 | ASSERT_EQ(0, m->consumption()); |
274 | 1 | } |
275 | | |
276 | 1 | TEST(MemTrackerTest, SoftLimitExceeded) { |
277 | 1 | const int kNumIters = 100000; |
278 | 1 | const int kMemLimit = 1000; |
279 | 1 | google::FlagSaver saver; |
280 | 1 | FLAGS_memory_limit_soft_percentage = 0; |
281 | 1 | shared_ptr<MemTracker> m = MemTracker::CreateTracker(kMemLimit, "test"); |
282 | | |
283 | | // Consumption is 0; the soft limit is never exceeded. |
284 | 100k | for (int i = 0; i < kNumIters; i++) { |
285 | 100k | ASSERT_FALSE(m->SoftLimitExceeded(0.0 /* score */).exceeded); |
286 | 100k | } |
287 | | |
288 | | // Consumption is half of the actual limit, so we expect to exceed the soft |
289 | | // limit roughly half the time. |
290 | 1 | ScopedTrackedConsumption consumption(m, kMemLimit / 2); |
291 | 1 | int exceeded_count = 0; |
292 | 100k | for (int i = 0; i < kNumIters; i++) { |
293 | 100k | auto soft_limit_exceeded_result = m->SoftLimitExceeded(0.0 /* score */); |
294 | 100k | if (soft_limit_exceeded_result.exceeded) { |
295 | 49.7k | exceeded_count++; |
296 | 49.7k | ASSERT_NEAR(50, soft_limit_exceeded_result.current_capacity_pct, 0.1); |
297 | 49.7k | } |
298 | 100k | } |
299 | 1 | double exceeded_pct = static_cast<double>(exceeded_count) / kNumIters * 100; |
300 | 1 | ASSERT_TRUE(exceeded_pct > 47 && exceeded_pct < 52); |
301 | | |
302 | | // Consumption is over the limit; the soft limit is always exceeded. |
303 | 1 | consumption.Reset(kMemLimit + 1); |
304 | 100k | for (int i = 0; i < kNumIters; i++) { |
305 | 100k | auto soft_limit_exceeded_result = m->SoftLimitExceeded(0.0 /* score */); |
306 | 100k | ASSERT_TRUE(soft_limit_exceeded_result.exceeded); |
307 | 100k | ASSERT_NEAR(100, soft_limit_exceeded_result.current_capacity_pct, 0.1); |
308 | 100k | } |
309 | 1 | } |
310 | | |
311 | | #ifdef TCMALLOC_ENABLED |
312 | | TEST(MemTrackerTest, TcMallocRootTracker) { |
313 | | const auto kWaitTimeout = std::chrono::microseconds( |
314 | | FLAGS_mem_tracker_update_consumption_interval_us * 2); |
315 | | shared_ptr<MemTracker> root = MemTracker::GetRootTracker(); |
316 | | |
317 | | // The root tracker's consumption and tcmalloc should agree. |
318 | | // Sleep to be sure that UpdateConsumption will take action. |
319 | | int64_t value = 0; |
320 | | ASSERT_OK(WaitFor([root, &value] { |
321 | | value = MemTracker::GetTCMallocActualHeapSizeBytes(); |
322 | | return root->GetUpdatedConsumption() == value; |
323 | | }, kWaitTimeout, "Consumption actualized")); |
324 | | |
325 | | // Explicit Consume() and Release() have no effect. |
326 | | root->Consume(100); |
327 | | ASSERT_EQ(value, root->consumption()); |
328 | | root->Release(3); |
329 | | ASSERT_EQ(value, root->consumption()); |
330 | | |
331 | | // But if we allocate something really big, we should see a change. |
332 | | std::unique_ptr<char[]> big_alloc(new char[4_MB]); |
333 | | // clang in release mode can optimize out the above allocation unless |
334 | | // we do something with the pointer... so we just log it. |
335 | | VLOG(8) << static_cast<void*>(big_alloc.get()); |
336 | | ASSERT_OK(WaitFor([root, value] { |
337 | | return root->GetUpdatedConsumption() > value; |
338 | | }, kWaitTimeout, "Consumption increased")); |
339 | | } |
340 | | |
341 | | TEST(MemTrackerTest, TcMallocGC) { |
342 | | shared_ptr<MemTracker> root = MemTracker::GetRootTracker(); |
343 | | // Set a low GC threshold, so we can manage it easily in the test. |
344 | | FLAGS_mem_tracker_tcmalloc_gc_release_bytes = 1_MB; |
345 | | // Allocate something bigger than the threshold. |
346 | | std::unique_ptr<char[]> big_alloc(new char[4_MB]); |
347 | | // clang in release mode can optimize out the above allocation unless |
348 | | // we do something with the pointer... so we just log it. |
349 | | VLOG(8) << static_cast<void*>(big_alloc.get()); |
350 | | // Check overhead at start of the test. |
351 | | auto overhead_before = MemTracker::GetTCMallocProperty("tcmalloc.pageheap_free_bytes"); |
352 | | LOG(INFO) << "Initial overhead " << overhead_before; |
353 | | // Clear the memory, so tcmalloc gets free bytes. |
354 | | big_alloc.reset(); |
355 | | // Check the overhead afterwards, should clearly be higher. |
356 | | auto overhead_after = MemTracker::GetTCMallocProperty("tcmalloc.pageheap_free_bytes"); |
357 | | LOG(INFO) << "Post-free overhead " << overhead_after; |
358 | | ASSERT_GT(overhead_after, overhead_before); |
359 | | // Release up to the threshold. We only GC after we cross it, so nothing should happen now. |
360 | | root->Release(1_MB); |
361 | | ASSERT_EQ(overhead_after, MemTracker::GetTCMallocProperty("tcmalloc.pageheap_free_bytes")); |
362 | | // Now we go over the limit and trigger a GC. |
363 | | root->Release(1); |
364 | | auto overhead_final = MemTracker::GetTCMallocProperty("tcmalloc.pageheap_free_bytes"); |
365 | | LOG(INFO) << "Final overhead " << overhead_final; |
366 | | ASSERT_GT(overhead_after, overhead_final); |
367 | | } |
368 | | #endif |
369 | | |
370 | 1 | TEST(MemTrackerTest, UnregisterFromParent) { |
371 | 1 | shared_ptr<MemTracker> p = MemTracker::CreateTracker("parent"); |
372 | 1 | shared_ptr<MemTracker> c = MemTracker::CreateTracker("child", p); |
373 | | |
374 | | // Three trackers: root, parent, and child. |
375 | 1 | auto all = MemTracker::ListTrackers(); |
376 | 2 | ASSERT_EQ(3, all.size()) << "All: " << yb::ToString(all); |
377 | | |
378 | 1 | c.reset(); |
379 | 1 | all.clear(); |
380 | | |
381 | | // Now only two because the child cannot be found from the root, though it is |
382 | | // still alive. |
383 | 1 | all = MemTracker::ListTrackers(); |
384 | 1 | ASSERT_EQ(2, all.size()); |
385 | 1 | ASSERT_TRUE(MemTracker::FindTracker("child", p) == nullptr); |
386 | | |
387 | | // We can also recreate the child with the same name without colliding |
388 | | // with the old one. |
389 | 1 | shared_ptr<MemTracker> c2 = MemTracker::CreateTracker("child", p); |
390 | 1 | } |
391 | | |
392 | | } // namespace yb |