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