1 // Copyright 2018 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/sampling_heap_profiler/sampling_heap_profiler.h"
6
7 #include <stdlib.h>
8 #include <cinttypes>
9
10 #include "base/allocator/dispatcher/dispatcher.h"
11 #include "base/allocator/partition_allocator/src/partition_alloc/shim/allocator_shim.h"
12 #include "base/debug/alias.h"
13 #include "base/memory/raw_ptr.h"
14 #include "base/rand_util.h"
15 #include "base/sampling_heap_profiler/poisson_allocation_sampler.h"
16 #include "base/synchronization/waitable_event.h"
17 #include "base/threading/simple_thread.h"
18 #include "build/build_config.h"
19 #include "testing/gtest/include/gtest/gtest.h"
20
21
22 namespace base {
23
24 using ScopedSuppressRandomnessForTesting =
25 PoissonAllocationSampler::ScopedSuppressRandomnessForTesting;
26 using base::allocator::dispatcher::AllocationSubsystem;
27
28 class SamplingHeapProfilerTest : public ::testing::Test {
29 public:
SetUp()30 void SetUp() override {
31 #if BUILDFLAG(IS_APPLE)
32 allocator_shim::InitializeAllocatorShim();
33 #endif
34 SamplingHeapProfiler::Init();
35
36 // Ensure the PoissonAllocationSampler starts in the default state.
37 ASSERT_FALSE(PoissonAllocationSampler::AreHookedSamplesMuted());
38 ASSERT_FALSE(PoissonAllocationSampler::ScopedMuteThreadSamples::IsMuted());
39 ASSERT_FALSE(ScopedSuppressRandomnessForTesting::IsSuppressed());
40
41 allocator::dispatcher::Dispatcher::GetInstance().InitializeForTesting(
42 PoissonAllocationSampler::Get());
43 }
44
TearDown()45 void TearDown() override {
46 allocator::dispatcher::Dispatcher::GetInstance().ResetForTesting();
47 }
48
GetNextSample(size_t mean_interval)49 size_t GetNextSample(size_t mean_interval) {
50 return PoissonAllocationSampler::GetNextSampleInterval(mean_interval);
51 }
52
GetRunningSessionsCount()53 static int GetRunningSessionsCount() {
54 return SamplingHeapProfiler::Get()->running_sessions_;
55 }
56
RunStartStopLoop(SamplingHeapProfiler * profiler)57 static void RunStartStopLoop(SamplingHeapProfiler* profiler) {
58 for (int i = 0; i < 100000; ++i) {
59 profiler->Start();
60 EXPECT_LE(1, GetRunningSessionsCount());
61 profiler->Stop();
62 }
63 }
64 };
65
66 class SamplesCollector : public PoissonAllocationSampler::SamplesObserver {
67 public:
SamplesCollector(size_t watch_size)68 explicit SamplesCollector(size_t watch_size) : watch_size_(watch_size) {}
69
SampleAdded(void * address,size_t size,size_t,AllocationSubsystem,const char *)70 void SampleAdded(void* address,
71 size_t size,
72 size_t,
73 AllocationSubsystem,
74 const char*) override {
75 if (sample_added || size != watch_size_)
76 return;
77 sample_address_ = address;
78 sample_added = true;
79 }
80
SampleRemoved(void * address)81 void SampleRemoved(void* address) override {
82 if (address == sample_address_)
83 sample_removed = true;
84 }
85
86 bool sample_added = false;
87 bool sample_removed = false;
88
89 private:
90 size_t watch_size_;
91 raw_ptr<void, DanglingUntriaged> sample_address_ = nullptr;
92 };
93
TEST_F(SamplingHeapProfilerTest,SampleObserver)94 TEST_F(SamplingHeapProfilerTest, SampleObserver) {
95 ScopedSuppressRandomnessForTesting suppress;
96 SamplesCollector collector(10000);
97 auto* sampler = PoissonAllocationSampler::Get();
98 sampler->SetSamplingInterval(1024);
99 sampler->AddSamplesObserver(&collector);
100 void* volatile p = malloc(10000);
101 free(p);
102 sampler->RemoveSamplesObserver(&collector);
103 EXPECT_TRUE(collector.sample_added);
104 EXPECT_TRUE(collector.sample_removed);
105 }
106
TEST_F(SamplingHeapProfilerTest,SampleObserverMuted)107 TEST_F(SamplingHeapProfilerTest, SampleObserverMuted) {
108 ScopedSuppressRandomnessForTesting suppress;
109 SamplesCollector collector(10000);
110 auto* sampler = PoissonAllocationSampler::Get();
111 sampler->SetSamplingInterval(1024);
112 sampler->AddSamplesObserver(&collector);
113 {
114 PoissonAllocationSampler::ScopedMuteThreadSamples muted_scope;
115 void* volatile p = malloc(10000);
116 free(p);
117 }
118 sampler->RemoveSamplesObserver(&collector);
119 EXPECT_FALSE(collector.sample_added);
120 EXPECT_FALSE(collector.sample_removed);
121 }
122
TEST_F(SamplingHeapProfilerTest,IntervalRandomizationSanity)123 TEST_F(SamplingHeapProfilerTest, IntervalRandomizationSanity) {
124 ASSERT_FALSE(ScopedSuppressRandomnessForTesting::IsSuppressed());
125 constexpr int iterations = 50;
126 constexpr size_t target = 10000000;
127 int sum = 0;
128 for (int i = 0; i < iterations; ++i) {
129 int samples = 0;
130 for (size_t value = 0; value < target; value += GetNextSample(10000))
131 ++samples;
132 // There are should be ~ target/10000 = 1000 samples.
133 sum += samples;
134 }
135 int mean_samples = sum / iterations;
136 EXPECT_NEAR(1000, mean_samples, 100); // 10% tolerance.
137 }
138
139 #if BUILDFLAG(IS_IOS)
140 // iOS devices generally have ~4GB of RAM with no swap and therefore need a
141 // lower allocation limit here.
142 const int kNumberOfAllocations = 1000;
143 #else
144 const int kNumberOfAllocations = 10000;
145 #endif
146
Allocate1()147 NOINLINE void Allocate1() {
148 void* p = malloc(400);
149 base::debug::Alias(&p);
150 }
151
Allocate2()152 NOINLINE void Allocate2() {
153 void* p = malloc(700);
154 base::debug::Alias(&p);
155 }
156
Allocate3()157 NOINLINE void Allocate3() {
158 void* p = malloc(20480);
159 base::debug::Alias(&p);
160 }
161
162 class MyThread1 : public SimpleThread {
163 public:
MyThread1()164 MyThread1() : SimpleThread("MyThread1") {}
Run()165 void Run() override {
166 for (int i = 0; i < kNumberOfAllocations; ++i)
167 Allocate1();
168 }
169 };
170
171 class MyThread2 : public SimpleThread {
172 public:
MyThread2()173 MyThread2() : SimpleThread("MyThread2") {}
Run()174 void Run() override {
175 for (int i = 0; i < kNumberOfAllocations; ++i)
176 Allocate2();
177 }
178 };
179
CheckAllocationPattern(void (* allocate_callback)())180 void CheckAllocationPattern(void (*allocate_callback)()) {
181 ASSERT_FALSE(ScopedSuppressRandomnessForTesting::IsSuppressed());
182 auto* profiler = SamplingHeapProfiler::Get();
183 profiler->SetSamplingInterval(10240);
184 base::TimeTicks t0 = base::TimeTicks::Now();
185 std::map<size_t, size_t> sums;
186 const int iterations = 40;
187 for (int i = 0; i < iterations; ++i) {
188 uint32_t id = profiler->Start();
189 allocate_callback();
190 std::vector<SamplingHeapProfiler::Sample> samples =
191 profiler->GetSamples(id);
192 profiler->Stop();
193 std::map<size_t, size_t> buckets;
194 for (auto& sample : samples) {
195 buckets[sample.size] += sample.total;
196 }
197 for (auto& it : buckets) {
198 if (it.first != 400 && it.first != 700 && it.first != 20480)
199 continue;
200 sums[it.first] += it.second;
201 printf("%zu,", it.second);
202 }
203 printf("\n");
204 }
205
206 printf("Time taken %" PRIu64 "ms\n",
207 (base::TimeTicks::Now() - t0).InMilliseconds());
208
209 for (auto sum : sums) {
210 intptr_t expected = sum.first * kNumberOfAllocations;
211 intptr_t actual = sum.second / iterations;
212 printf("%zu:\tmean: %zu\trelative error: %.2f%%\n", sum.first, actual,
213 100. * (actual - expected) / expected);
214 }
215 }
216
217 // Manual tests to check precision of the sampling profiler.
218 // Yes, they do leak lots of memory.
219
TEST_F(SamplingHeapProfilerTest,DISABLED_ParallelLargeSmallStats)220 TEST_F(SamplingHeapProfilerTest, DISABLED_ParallelLargeSmallStats) {
221 CheckAllocationPattern([]() {
222 MyThread1 t1;
223 MyThread1 t2;
224 t1.Start();
225 t2.Start();
226 for (int i = 0; i < kNumberOfAllocations; ++i)
227 Allocate3();
228 t1.Join();
229 t2.Join();
230 });
231 }
232
TEST_F(SamplingHeapProfilerTest,DISABLED_SequentialLargeSmallStats)233 TEST_F(SamplingHeapProfilerTest, DISABLED_SequentialLargeSmallStats) {
234 CheckAllocationPattern([]() {
235 for (int i = 0; i < kNumberOfAllocations; ++i) {
236 Allocate1();
237 Allocate2();
238 Allocate3();
239 }
240 });
241 }
242
243 // Platform TLS: alloc+free[ns]: 22.184 alloc[ns]: 8.910 free[ns]: 13.274
244 // thread_local: alloc+free[ns]: 18.353 alloc[ns]: 5.021 free[ns]: 13.331
245 // TODO(crbug.com/1117342) Disabled on Mac
246 #if BUILDFLAG(IS_MAC)
247 #define MAYBE_MANUAL_SamplerMicroBenchmark DISABLED_MANUAL_SamplerMicroBenchmark
248 #else
249 #define MAYBE_MANUAL_SamplerMicroBenchmark MANUAL_SamplerMicroBenchmark
250 #endif
TEST_F(SamplingHeapProfilerTest,MAYBE_MANUAL_SamplerMicroBenchmark)251 TEST_F(SamplingHeapProfilerTest, MAYBE_MANUAL_SamplerMicroBenchmark) {
252 // With the sampling interval of 100KB it happens to record ~ every 450th
253 // allocation in the browser process. We model this pattern here.
254 constexpr size_t sampling_interval = 100000;
255 constexpr size_t allocation_size = sampling_interval / 450;
256 SamplesCollector collector(0);
257 auto* sampler = PoissonAllocationSampler::Get();
258 sampler->SetSamplingInterval(sampling_interval);
259 sampler->AddSamplesObserver(&collector);
260 int kNumAllocations = 50000000;
261
262 base::TimeTicks t0 = base::TimeTicks::Now();
263 for (int i = 1; i <= kNumAllocations; ++i) {
264 sampler->OnAllocation(reinterpret_cast<void*>(static_cast<intptr_t>(i)),
265 allocation_size, AllocationSubsystem::kAllocatorShim,
266 nullptr);
267 }
268 base::TimeTicks t1 = base::TimeTicks::Now();
269 for (int i = 1; i <= kNumAllocations; ++i)
270 sampler->OnFree(reinterpret_cast<void*>(static_cast<intptr_t>(i)));
271 base::TimeTicks t2 = base::TimeTicks::Now();
272
273 printf(
274 "alloc+free[ns]: %.3f alloc[ns]: %.3f free[ns]: %.3f "
275 "alloc+free[mln/s]: %.1f total[ms]: %.1f\n",
276 (t2 - t0).InNanoseconds() * 1. / kNumAllocations,
277 (t1 - t0).InNanoseconds() * 1. / kNumAllocations,
278 (t2 - t1).InNanoseconds() * 1. / kNumAllocations,
279 kNumAllocations / (t2 - t0).InMicrosecondsF(),
280 (t2 - t0).InMillisecondsF());
281
282 sampler->RemoveSamplesObserver(&collector);
283 }
284
285 class StartStopThread : public SimpleThread {
286 public:
StartStopThread(WaitableEvent * event)287 StartStopThread(WaitableEvent* event)
288 : SimpleThread("MyThread2"), event_(event) {}
Run()289 void Run() override {
290 auto* profiler = SamplingHeapProfiler::Get();
291 event_->Signal();
292 SamplingHeapProfilerTest::RunStartStopLoop(profiler);
293 }
294
295 private:
296 raw_ptr<WaitableEvent> event_;
297 };
298
TEST_F(SamplingHeapProfilerTest,StartStop)299 TEST_F(SamplingHeapProfilerTest, StartStop) {
300 auto* profiler = SamplingHeapProfiler::Get();
301 EXPECT_EQ(0, GetRunningSessionsCount());
302 profiler->Start();
303 EXPECT_EQ(1, GetRunningSessionsCount());
304 profiler->Start();
305 EXPECT_EQ(2, GetRunningSessionsCount());
306 profiler->Stop();
307 EXPECT_EQ(1, GetRunningSessionsCount());
308 profiler->Stop();
309 EXPECT_EQ(0, GetRunningSessionsCount());
310 }
311
312 // TODO(crbug.com/1116543): Test is crashing on Mac.
313 #if BUILDFLAG(IS_MAC)
314 #define MAYBE_ConcurrentStartStop DISABLED_ConcurrentStartStop
315 #else
316 #define MAYBE_ConcurrentStartStop ConcurrentStartStop
317 #endif
TEST_F(SamplingHeapProfilerTest,MAYBE_ConcurrentStartStop)318 TEST_F(SamplingHeapProfilerTest, MAYBE_ConcurrentStartStop) {
319 auto* profiler = SamplingHeapProfiler::Get();
320 WaitableEvent event;
321 StartStopThread thread(&event);
322 thread.Start();
323 event.Wait();
324 RunStartStopLoop(profiler);
325 thread.Join();
326 EXPECT_EQ(0, GetRunningSessionsCount());
327 }
328
TEST_F(SamplingHeapProfilerTest,HookedAllocatorMuted)329 TEST_F(SamplingHeapProfilerTest, HookedAllocatorMuted) {
330 ScopedSuppressRandomnessForTesting suppress;
331 EXPECT_FALSE(PoissonAllocationSampler::AreHookedSamplesMuted());
332
333 auto* sampler = PoissonAllocationSampler::Get();
334 sampler->SetSamplingInterval(1024);
335
336 {
337 PoissonAllocationSampler::ScopedMuteHookedSamplesForTesting mute_hooks;
338 EXPECT_TRUE(PoissonAllocationSampler::AreHookedSamplesMuted());
339
340 SamplesCollector collector(10000);
341
342 // A ScopedMuteHookedSamplesForTesting exists so hooked allocations should
343 // be ignored.
344 sampler->AddSamplesObserver(&collector);
345 void* volatile p = malloc(10000);
346 free(p);
347 sampler->RemoveSamplesObserver(&collector);
348 EXPECT_FALSE(collector.sample_added);
349 EXPECT_FALSE(collector.sample_removed);
350
351 // Manual allocations should be captured.
352 sampler->AddSamplesObserver(&collector);
353 void* const kAddress = reinterpret_cast<void*>(0x1234);
354 sampler->OnAllocation(kAddress, 10000,
355 AllocationSubsystem::kManualForTesting, nullptr);
356 sampler->OnFree(kAddress);
357 sampler->RemoveSamplesObserver(&collector);
358 EXPECT_TRUE(collector.sample_added);
359 EXPECT_TRUE(collector.sample_removed);
360 }
361
362 EXPECT_FALSE(PoissonAllocationSampler::AreHookedSamplesMuted());
363
364 // Hooked allocations should be captured again.
365 SamplesCollector collector(10000);
366 sampler->AddSamplesObserver(&collector);
367 void* volatile p = malloc(10000);
368 free(p);
369 sampler->RemoveSamplesObserver(&collector);
370 EXPECT_TRUE(collector.sample_added);
371 EXPECT_TRUE(collector.sample_removed);
372 }
373
374 } // namespace base
375