YugabyteDB (2.13.0.0-b42, bfc6a6643e7399ac8a0e81d06a3ee6d6571b33ab)

Coverage Report

Created: 2022-03-09 17:30

/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