• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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