/Users/deen/code/yugabyte-db/src/yb/util/memory/arena-test.cc
Line | Count | Source (jump to first uncovered line) |
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 | | #include <memory> |
33 | | #include <thread> |
34 | | #include <vector> |
35 | | |
36 | | #include <glog/logging.h> |
37 | | #include <gtest/gtest.h> |
38 | | |
39 | | #include "yb/util/mem_tracker.h" |
40 | | #include "yb/util/memory/arena.h" |
41 | | #include "yb/util/memory/mc_types.h" |
42 | | #include "yb/util/memory/memory.h" |
43 | | |
44 | | DEFINE_int32(num_threads, 16, "Number of threads to test"); |
45 | | DEFINE_int32(allocs_per_thread, 10000, "Number of allocations each thread should do"); |
46 | | DEFINE_int32(alloc_size, 4, "number of bytes in each allocation"); |
47 | | |
48 | | namespace yb { |
49 | | |
50 | | using std::shared_ptr; |
51 | | |
52 | | namespace { |
53 | | |
54 | | // Updates external counter when object is created/destroyed. |
55 | | // So one could check whether all objects is destoyed. |
56 | | class Trackable { |
57 | | public: |
58 | | explicit Trackable(int* counter) |
59 | 7 | : counter_(counter) { |
60 | 7 | ++*counter_; |
61 | 7 | } |
62 | | |
63 | | Trackable(const Trackable& rhs) |
64 | 2 | : counter_(rhs.counter_) { |
65 | 2 | ++*counter_; |
66 | 2 | } |
67 | | |
68 | 0 | Trackable& operator=(const Trackable& rhs) { |
69 | 0 | --*counter_; |
70 | 0 | counter_ = rhs.counter_; |
71 | 0 | ++*counter_; |
72 | 0 | return *this; |
73 | 0 | } |
74 | | |
75 | 9 | ~Trackable() { |
76 | 9 | --*counter_; |
77 | 9 | } |
78 | | |
79 | | private: |
80 | | int* counter_; |
81 | | }; |
82 | | |
83 | | // Checks that counter is zero on destruction, so all objects is destroyed. |
84 | | class CounterHolder { |
85 | | public: |
86 | | int counter = 0; |
87 | | |
88 | 7 | ~CounterHolder() { |
89 | 7 | CheckCounter(); |
90 | 7 | } |
91 | | private: |
92 | 7 | void CheckCounter() { |
93 | 7 | ASSERT_EQ(0, counter); |
94 | 7 | } |
95 | | }; |
96 | | |
97 | | class CountedArena : public CounterHolder, public Arena { |
98 | | }; |
99 | | |
100 | | template<class ArenaType> |
101 | 16 | void AllocateThread(ArenaType *arena, uint8_t thread_index) { |
102 | 16 | std::vector<void *> ptrs; |
103 | 16 | ptrs.reserve(FLAGS_allocs_per_thread); |
104 | | |
105 | 16 | char buf[FLAGS_alloc_size]; |
106 | 16 | memset(buf, thread_index, FLAGS_alloc_size); |
107 | | |
108 | 168k | for (int i = 0; i < FLAGS_allocs_per_thread; i++) { |
109 | 168k | void *alloced = arena->AllocateBytes(FLAGS_alloc_size); |
110 | 168k | CHECK(alloced); |
111 | 168k | memcpy(alloced, buf, FLAGS_alloc_size); |
112 | 168k | ptrs.push_back(alloced); |
113 | 168k | } |
114 | | |
115 | 161k | for (void *p : ptrs) { |
116 | 161k | if (memcmp(buf, p, FLAGS_alloc_size) != 0) { |
117 | 0 | FAIL() << StringPrintf("overwritten pointer at %p", p); |
118 | 0 | } |
119 | 161k | } |
120 | 16 | } arena-test.cc:_ZN2yb12_GLOBAL__N_114AllocateThreadINS_8internal9ArenaBaseINS2_21ThreadSafeArenaTraitsEEEEEvPT_h Line | Count | Source | 101 | 15 | void AllocateThread(ArenaType *arena, uint8_t thread_index) { | 102 | 15 | std::vector<void *> ptrs; | 103 | 15 | ptrs.reserve(FLAGS_allocs_per_thread); | 104 | | | 105 | 15 | char buf[FLAGS_alloc_size]; | 106 | 15 | memset(buf, thread_index, FLAGS_alloc_size); | 107 | | | 108 | 158k | for (int i = 0; i < FLAGS_allocs_per_thread; i++) { | 109 | 158k | void *alloced = arena->AllocateBytes(FLAGS_alloc_size); | 110 | 158k | CHECK(alloced); | 111 | 158k | memcpy(alloced, buf, FLAGS_alloc_size); | 112 | 158k | ptrs.push_back(alloced); | 113 | 158k | } | 114 | | | 115 | 151k | for (void *p : ptrs) { | 116 | 151k | if (memcmp(buf, p, FLAGS_alloc_size) != 0) { | 117 | 0 | FAIL() << StringPrintf("overwritten pointer at %p", p); | 118 | 0 | } | 119 | 151k | } | 120 | 15 | } |
arena-test.cc:_ZN2yb12_GLOBAL__N_114AllocateThreadINS_8internal9ArenaBaseINS2_11ArenaTraitsEEEEEvPT_h Line | Count | Source | 101 | 1 | void AllocateThread(ArenaType *arena, uint8_t thread_index) { | 102 | 1 | std::vector<void *> ptrs; | 103 | 1 | ptrs.reserve(FLAGS_allocs_per_thread); | 104 | | | 105 | 1 | char buf[FLAGS_alloc_size]; | 106 | 1 | memset(buf, thread_index, FLAGS_alloc_size); | 107 | | | 108 | 10.0k | for (int i = 0; i < FLAGS_allocs_per_thread; i++) { | 109 | 10.0k | void *alloced = arena->AllocateBytes(FLAGS_alloc_size); | 110 | 10.0k | CHECK(alloced); | 111 | 10.0k | memcpy(alloced, buf, FLAGS_alloc_size); | 112 | 10.0k | ptrs.push_back(alloced); | 113 | 10.0k | } | 114 | | | 115 | 10.0k | for (void *p : ptrs) { | 116 | 10.0k | if (memcmp(buf, p, FLAGS_alloc_size) != 0) { | 117 | 0 | FAIL() << StringPrintf("overwritten pointer at %p", p); | 118 | 0 | } | 119 | 10.0k | } | 120 | 1 | } |
|
121 | | |
122 | | // Non-templated function to forward to above -- simplifies thread creation |
123 | 16 | void AllocateThreadTSArena(ThreadSafeArena *arena, uint8_t thread_index) { |
124 | 16 | AllocateThread(arena, thread_index); |
125 | 16 | } |
126 | | |
127 | | constexpr size_t component_size = sizeof(internal::ArenaComponent<internal::ArenaTraits>); |
128 | | |
129 | | } // namespace |
130 | | |
131 | 1 | TEST(TestArena, TestSingleThreaded) { |
132 | 1 | Arena arena(128, 128); |
133 | | |
134 | 1 | AllocateThread(&arena, 0); |
135 | 1 | } |
136 | | |
137 | 1 | TEST(TestArena, TestMultiThreaded) { |
138 | 1 | CHECK_LT(FLAGS_num_threads, 256); |
139 | | |
140 | 1 | ThreadSafeArena arena(1024, 1024); |
141 | | |
142 | 1 | std::vector<std::thread> threads; |
143 | 17 | for (uint8_t i = 0; i < FLAGS_num_threads; i++) { |
144 | 16 | threads.emplace_back(std::bind(AllocateThreadTSArena, &arena, (uint8_t)i)); |
145 | 16 | } |
146 | | |
147 | 16 | for (auto& thr : threads) { |
148 | 16 | thr.join(); |
149 | 16 | } |
150 | 1 | } |
151 | | |
152 | 1 | TEST(TestArena, TestAlignment) { |
153 | | |
154 | 1 | ThreadSafeArena arena(1024, 1024); |
155 | 1.00k | for (int i = 0; i < 1000; i++) { |
156 | 1.00k | int alignment = 1 << (1 % 5); |
157 | | |
158 | 1.00k | void *ret = arena.AllocateBytesAligned(5, alignment); |
159 | 2.00k | ASSERT_EQ(0, (uintptr_t)(ret) % alignment) << |
160 | 2.00k | "failed to align on " << alignment << "b boundary: " << |
161 | 2.00k | ret; |
162 | 1.00k | } |
163 | 1 | } |
164 | | |
165 | 2 | void TestAllocations(const shared_ptr<MemTracker>& tracker, Arena* arena) { |
166 | | // Try some child operations. |
167 | 2 | ASSERT_EQ(256, tracker->consumption()); |
168 | 2 | void *allocated = arena->AllocateBytes(256 - component_size); |
169 | 2 | ASSERT_TRUE(allocated); |
170 | 2 | ASSERT_EQ(256, tracker->consumption()); |
171 | 2 | allocated = arena->AllocateBytes(256); |
172 | 2 | ASSERT_NE(allocated, nullptr); |
173 | 2 | ASSERT_EQ(768, tracker->consumption()); |
174 | 2 | } |
175 | | |
176 | | // MemTrackers update their ancestors when consuming and releasing memory to compute |
177 | | // usage totals. However, the lifetimes of parent and child trackers can be different. |
178 | | // Validate that child trackers can still correctly update their parent stats even when |
179 | | // the parents go out of scope. |
180 | 1 | TEST(TestArena, TestMemoryTrackerParentReferences) { |
181 | | // Set up a parent and child MemTracker. |
182 | 1 | const string parent_id = "parent-id"; |
183 | 1 | const string child_id = "child-id"; |
184 | 1 | shared_ptr<MemTracker> child_tracker; |
185 | 1 | { |
186 | 1 | shared_ptr<MemTracker> parent_tracker = MemTracker::CreateTracker(1024, parent_id); |
187 | 1 | child_tracker = MemTracker::CreateTracker(child_id, parent_tracker); |
188 | | // Parent falls out of scope here. Should still be owned by the child. |
189 | 1 | } |
190 | 1 | shared_ptr<MemoryTrackingBufferAllocator> allocator( |
191 | 1 | new MemoryTrackingBufferAllocator(HeapBufferAllocator::Get(), child_tracker)); |
192 | 1 | Arena arena(allocator.get(), 256, 1024); |
193 | | |
194 | 1 | TestAllocations(child_tracker, &arena); |
195 | 1 | } |
196 | | |
197 | 1 | TEST(TestArena, TestMemoryTrackingDontEnforce) { |
198 | 1 | shared_ptr<MemTracker> mem_tracker = MemTracker::CreateTracker(1024, "arena-test-tracker"); |
199 | 1 | shared_ptr<MemoryTrackingBufferAllocator> allocator( |
200 | 1 | new MemoryTrackingBufferAllocator(HeapBufferAllocator::Get(), mem_tracker)); |
201 | 1 | Arena arena(allocator.get(), 256, 1024); |
202 | 1 | TestAllocations(mem_tracker, &arena); |
203 | | |
204 | | // In DEBUG mode after Reset() the last component of an arena is |
205 | | // cleared, but is then created again; in release mode, the last |
206 | | // component is not cleared. In either case, after Reset() |
207 | | // consumption() should equal the size of the last component which |
208 | | // is 512 bytes. |
209 | 1 | arena.Reset(); |
210 | 1 | ASSERT_EQ(512, mem_tracker->consumption()); |
211 | | |
212 | | // Allocate beyond allowed consumption. This should still go |
213 | | // through, since enforce_limit is false. |
214 | 1 | auto allocated = arena.AllocateBytes(1024 - component_size); |
215 | 1 | ASSERT_TRUE(allocated); |
216 | | |
217 | 1 | ASSERT_EQ(1536, mem_tracker->consumption()); |
218 | 1 | } |
219 | | |
220 | 1 | TEST(TestArena, TestMemoryTrackingEnforced) { |
221 | 1 | shared_ptr<MemTracker> mem_tracker = MemTracker::CreateTracker(1024, "arena-test-tracker"); |
222 | 1 | shared_ptr<MemoryTrackingBufferAllocator> allocator( |
223 | 1 | new MemoryTrackingBufferAllocator(HeapBufferAllocator::Get(), mem_tracker, |
224 | | // enforce limit |
225 | 1 | true)); |
226 | 1 | Arena arena(allocator.get(), 256, 1024); |
227 | 1 | ASSERT_EQ(256, mem_tracker->consumption()); |
228 | 1 | void *allocated = arena.AllocateBytes(256 - component_size); |
229 | 1 | ASSERT_TRUE(allocated); |
230 | 1 | ASSERT_EQ(256, mem_tracker->consumption()); |
231 | 1 | allocated = arena.AllocateBytes(1024 - component_size); |
232 | 1 | ASSERT_EQ(allocated, nullptr); |
233 | 1 | ASSERT_EQ(256, mem_tracker->consumption()); |
234 | 1 | } |
235 | | |
236 | 1 | TEST(TestArena, TestSTLAllocator) { |
237 | 1 | Arena a(256, 256 * 1024); |
238 | 1 | typedef vector<int, ArenaAllocator<int>> ArenaVector; |
239 | 1 | ArenaAllocator<int> alloc(&a); |
240 | 1 | ArenaVector v(alloc); |
241 | 10.0k | for (int i = 0; i < 10000; i++) { |
242 | 10.0k | v.push_back(i); |
243 | 10.0k | } |
244 | 10.0k | for (int i = 0; i < 10000; i++) { |
245 | 10.0k | ASSERT_EQ(i, v[i]); |
246 | 10.0k | } |
247 | 1 | } |
248 | | |
249 | 1 | TEST(TestArena, TestUniquePtr) { |
250 | 1 | CountedArena ca; |
251 | 1 | MCUniPtr<Trackable> trackable(ca.NewObject<Trackable>(&ca.counter)); |
252 | 1 | ASSERT_EQ(1, ca.counter); |
253 | 1 | } |
254 | | |
255 | 1 | TEST(TestArena, TestAllocateShared) { |
256 | 1 | CountedArena ca; |
257 | 1 | auto trackable = ca.AllocateShared<Trackable>(&ca.counter); |
258 | 1 | ASSERT_EQ(1, ca.counter); |
259 | 1 | } |
260 | | |
261 | 1 | TEST(TestArena, TestToShared) { |
262 | 1 | CountedArena ca; |
263 | 1 | auto trackable = ca.ToShared(ca.NewObject<Trackable>(&ca.counter)); |
264 | 1 | ASSERT_EQ(1, ca.counter); |
265 | 1 | } |
266 | | |
267 | 1 | TEST(TestArena, TestVector) { |
268 | 1 | CountedArena ca; |
269 | 1 | MCVector<Trackable> vector(&ca); |
270 | 1 | vector.emplace_back(&ca.counter); |
271 | 1 | ASSERT_EQ(1, ca.counter); |
272 | 1 | } |
273 | | |
274 | 1 | TEST(TestArena, TestList) { |
275 | 1 | CountedArena ca; |
276 | 1 | MCList<Trackable> list(&ca); |
277 | 1 | list.emplace_back(&ca.counter); |
278 | 1 | ASSERT_EQ(1, ca.counter); |
279 | 1 | } |
280 | | |
281 | 1 | TEST(TestArena, TestMap) { |
282 | 1 | CountedArena ca; |
283 | 1 | MCMap<int, Trackable> map(&ca); |
284 | 1 | map.emplace(1, Trackable(&ca.counter)); |
285 | 1 | ASSERT_EQ(1, ca.counter); |
286 | 1 | } |
287 | | |
288 | 1 | TEST(TestArena, TestString) { |
289 | 1 | CountedArena ca; |
290 | 1 | MCMap<MCString, Trackable> map(&ca); |
291 | 1 | MCString one("1", &ca); |
292 | 1 | MCString ten("10", &ca); |
293 | 1 | map.emplace(one, Trackable(&ca.counter)); |
294 | 1 | ASSERT_EQ(1, ca.counter); |
295 | | |
296 | | // Check correctness of comparison operators. |
297 | 1 | ASSERT_LT(one, ten); |
298 | 1 | ASSERT_FALSE(one < one); |
299 | 1 | ASSERT_FALSE(ten < one); |
300 | | |
301 | 1 | ASSERT_LE(one, ten); |
302 | 1 | ASSERT_LE(one, one); |
303 | 1 | ASSERT_FALSE(ten <= one); |
304 | | |
305 | 1 | ASSERT_GE(ten, one); |
306 | 1 | ASSERT_GE(one, one); |
307 | 1 | ASSERT_FALSE(one >= ten); |
308 | | |
309 | 1 | ASSERT_GT(ten, one); |
310 | 1 | ASSERT_FALSE(one > one); |
311 | 1 | ASSERT_FALSE(one > ten); |
312 | 1 | } |
313 | | |
314 | | } // namespace yb |