123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364 |
- // Copyright 2018 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "base/sampling_heap_profiler/sampling_heap_profiler.h"
- #include <stdlib.h>
- #include <cinttypes>
- #include "base/allocator/allocator_shim.h"
- #include "base/debug/alias.h"
- #include "base/memory/raw_ptr.h"
- #include "base/rand_util.h"
- #include "base/sampling_heap_profiler/poisson_allocation_sampler.h"
- #include "base/synchronization/waitable_event.h"
- #include "base/threading/simple_thread.h"
- #include "build/build_config.h"
- #include "testing/gtest/include/gtest/gtest.h"
- namespace base {
- using ScopedSuppressRandomnessForTesting =
- PoissonAllocationSampler::ScopedSuppressRandomnessForTesting;
- class SamplingHeapProfilerTest : public ::testing::Test {
- public:
- void SetUp() override {
- #if BUILDFLAG(IS_APPLE)
- allocator::InitializeAllocatorShim();
- #endif
- SamplingHeapProfiler::Init();
- // Ensure the PoissonAllocationSampler starts in the default state.
- ASSERT_FALSE(PoissonAllocationSampler::AreHookedSamplesMuted());
- ASSERT_FALSE(PoissonAllocationSampler::ScopedMuteThreadSamples::IsMuted());
- ASSERT_FALSE(ScopedSuppressRandomnessForTesting::IsSuppressed());
- }
- size_t GetNextSample(size_t mean_interval) {
- return PoissonAllocationSampler::GetNextSampleInterval(mean_interval);
- }
- static int GetRunningSessionsCount() {
- return SamplingHeapProfiler::Get()->running_sessions_;
- }
- static void RunStartStopLoop(SamplingHeapProfiler* profiler) {
- for (int i = 0; i < 100000; ++i) {
- profiler->Start();
- EXPECT_LE(1, GetRunningSessionsCount());
- profiler->Stop();
- }
- }
- };
- class SamplesCollector : public PoissonAllocationSampler::SamplesObserver {
- public:
- explicit SamplesCollector(size_t watch_size) : watch_size_(watch_size) {}
- void SampleAdded(void* address,
- size_t size,
- size_t,
- PoissonAllocationSampler::AllocatorType,
- const char*) override {
- if (sample_added || size != watch_size_)
- return;
- sample_address_ = address;
- sample_added = true;
- }
- void SampleRemoved(void* address) override {
- if (address == sample_address_)
- sample_removed = true;
- }
- bool sample_added = false;
- bool sample_removed = false;
- private:
- size_t watch_size_;
- raw_ptr<void> sample_address_ = nullptr;
- };
- TEST_F(SamplingHeapProfilerTest, SampleObserver) {
- ScopedSuppressRandomnessForTesting suppress;
- SamplesCollector collector(10000);
- auto* sampler = PoissonAllocationSampler::Get();
- sampler->SetSamplingInterval(1024);
- sampler->AddSamplesObserver(&collector);
- void* volatile p = malloc(10000);
- free(p);
- sampler->RemoveSamplesObserver(&collector);
- EXPECT_TRUE(collector.sample_added);
- EXPECT_TRUE(collector.sample_removed);
- }
- TEST_F(SamplingHeapProfilerTest, SampleObserverMuted) {
- ScopedSuppressRandomnessForTesting suppress;
- SamplesCollector collector(10000);
- auto* sampler = PoissonAllocationSampler::Get();
- sampler->SetSamplingInterval(1024);
- sampler->AddSamplesObserver(&collector);
- {
- PoissonAllocationSampler::ScopedMuteThreadSamples muted_scope;
- void* volatile p = malloc(10000);
- free(p);
- }
- sampler->RemoveSamplesObserver(&collector);
- EXPECT_FALSE(collector.sample_added);
- EXPECT_FALSE(collector.sample_removed);
- }
- TEST_F(SamplingHeapProfilerTest, IntervalRandomizationSanity) {
- ASSERT_FALSE(ScopedSuppressRandomnessForTesting::IsSuppressed());
- constexpr int iterations = 50;
- constexpr size_t target = 10000000;
- int sum = 0;
- for (int i = 0; i < iterations; ++i) {
- int samples = 0;
- for (size_t value = 0; value < target; value += GetNextSample(10000))
- ++samples;
- // There are should be ~ target/10000 = 1000 samples.
- sum += samples;
- }
- int mean_samples = sum / iterations;
- EXPECT_NEAR(1000, mean_samples, 100); // 10% tolerance.
- }
- #if BUILDFLAG(IS_IOS)
- // iOS devices generally have ~4GB of RAM with no swap and therefore need a
- // lower allocation limit here.
- const int kNumberOfAllocations = 1000;
- #else
- const int kNumberOfAllocations = 10000;
- #endif
- NOINLINE void Allocate1() {
- void* p = malloc(400);
- base::debug::Alias(&p);
- }
- NOINLINE void Allocate2() {
- void* p = malloc(700);
- base::debug::Alias(&p);
- }
- NOINLINE void Allocate3() {
- void* p = malloc(20480);
- base::debug::Alias(&p);
- }
- class MyThread1 : public SimpleThread {
- public:
- MyThread1() : SimpleThread("MyThread1") {}
- void Run() override {
- for (int i = 0; i < kNumberOfAllocations; ++i)
- Allocate1();
- }
- };
- class MyThread2 : public SimpleThread {
- public:
- MyThread2() : SimpleThread("MyThread2") {}
- void Run() override {
- for (int i = 0; i < kNumberOfAllocations; ++i)
- Allocate2();
- }
- };
- void CheckAllocationPattern(void (*allocate_callback)()) {
- ASSERT_FALSE(ScopedSuppressRandomnessForTesting::IsSuppressed());
- auto* profiler = SamplingHeapProfiler::Get();
- profiler->SetSamplingInterval(10240);
- base::TimeTicks t0 = base::TimeTicks::Now();
- std::map<size_t, size_t> sums;
- const int iterations = 40;
- for (int i = 0; i < iterations; ++i) {
- uint32_t id = profiler->Start();
- allocate_callback();
- std::vector<SamplingHeapProfiler::Sample> samples =
- profiler->GetSamples(id);
- profiler->Stop();
- std::map<size_t, size_t> buckets;
- for (auto& sample : samples) {
- buckets[sample.size] += sample.total;
- }
- for (auto& it : buckets) {
- if (it.first != 400 && it.first != 700 && it.first != 20480)
- continue;
- sums[it.first] += it.second;
- printf("%zu,", it.second);
- }
- printf("\n");
- }
- printf("Time taken %" PRIu64 "ms\n",
- (base::TimeTicks::Now() - t0).InMilliseconds());
- for (auto sum : sums) {
- intptr_t expected = sum.first * kNumberOfAllocations;
- intptr_t actual = sum.second / iterations;
- printf("%zu:\tmean: %zu\trelative error: %.2f%%\n", sum.first, actual,
- 100. * (actual - expected) / expected);
- }
- }
- // Manual tests to check precision of the sampling profiler.
- // Yes, they do leak lots of memory.
- TEST_F(SamplingHeapProfilerTest, DISABLED_ParallelLargeSmallStats) {
- CheckAllocationPattern([]() {
- MyThread1 t1;
- MyThread1 t2;
- t1.Start();
- t2.Start();
- for (int i = 0; i < kNumberOfAllocations; ++i)
- Allocate3();
- t1.Join();
- t2.Join();
- });
- }
- TEST_F(SamplingHeapProfilerTest, DISABLED_SequentialLargeSmallStats) {
- CheckAllocationPattern([]() {
- for (int i = 0; i < kNumberOfAllocations; ++i) {
- Allocate1();
- Allocate2();
- Allocate3();
- }
- });
- }
- // Platform TLS: alloc+free[ns]: 22.184 alloc[ns]: 8.910 free[ns]: 13.274
- // thread_local: alloc+free[ns]: 18.353 alloc[ns]: 5.021 free[ns]: 13.331
- // TODO(crbug.com/1117342) Disabled on Mac
- #if BUILDFLAG(IS_MAC)
- #define MAYBE_MANUAL_SamplerMicroBenchmark DISABLED_MANUAL_SamplerMicroBenchmark
- #else
- #define MAYBE_MANUAL_SamplerMicroBenchmark MANUAL_SamplerMicroBenchmark
- #endif
- TEST_F(SamplingHeapProfilerTest, MAYBE_MANUAL_SamplerMicroBenchmark) {
- // With the sampling interval of 100KB it happens to record ~ every 450th
- // allocation in the browser process. We model this pattern here.
- constexpr size_t sampling_interval = 100000;
- constexpr size_t allocation_size = sampling_interval / 450;
- SamplesCollector collector(0);
- auto* sampler = PoissonAllocationSampler::Get();
- sampler->SetSamplingInterval(sampling_interval);
- sampler->AddSamplesObserver(&collector);
- int kNumAllocations = 50000000;
- base::TimeTicks t0 = base::TimeTicks::Now();
- for (int i = 1; i <= kNumAllocations; ++i) {
- sampler->RecordAlloc(
- reinterpret_cast<void*>(static_cast<intptr_t>(i)), allocation_size,
- PoissonAllocationSampler::AllocatorType::kMalloc, nullptr);
- }
- base::TimeTicks t1 = base::TimeTicks::Now();
- for (int i = 1; i <= kNumAllocations; ++i)
- sampler->RecordFree(reinterpret_cast<void*>(static_cast<intptr_t>(i)));
- base::TimeTicks t2 = base::TimeTicks::Now();
- printf(
- "alloc+free[ns]: %.3f alloc[ns]: %.3f free[ns]: %.3f "
- "alloc+free[mln/s]: %.1f total[ms]: %.1f\n",
- (t2 - t0).InNanoseconds() * 1. / kNumAllocations,
- (t1 - t0).InNanoseconds() * 1. / kNumAllocations,
- (t2 - t1).InNanoseconds() * 1. / kNumAllocations,
- kNumAllocations / (t2 - t0).InMicrosecondsF(),
- (t2 - t0).InMillisecondsF());
- sampler->RemoveSamplesObserver(&collector);
- }
- class StartStopThread : public SimpleThread {
- public:
- StartStopThread(WaitableEvent* event)
- : SimpleThread("MyThread2"), event_(event) {}
- void Run() override {
- auto* profiler = SamplingHeapProfiler::Get();
- event_->Signal();
- SamplingHeapProfilerTest::RunStartStopLoop(profiler);
- }
- private:
- raw_ptr<WaitableEvent> event_;
- };
- TEST_F(SamplingHeapProfilerTest, StartStop) {
- auto* profiler = SamplingHeapProfiler::Get();
- EXPECT_EQ(0, GetRunningSessionsCount());
- profiler->Start();
- EXPECT_EQ(1, GetRunningSessionsCount());
- profiler->Start();
- EXPECT_EQ(2, GetRunningSessionsCount());
- profiler->Stop();
- EXPECT_EQ(1, GetRunningSessionsCount());
- profiler->Stop();
- EXPECT_EQ(0, GetRunningSessionsCount());
- }
- // TODO(crbug.com/1116543): Test is crashing on Mac.
- #if BUILDFLAG(IS_MAC)
- #define MAYBE_ConcurrentStartStop DISABLED_ConcurrentStartStop
- #else
- #define MAYBE_ConcurrentStartStop ConcurrentStartStop
- #endif
- TEST_F(SamplingHeapProfilerTest, MAYBE_ConcurrentStartStop) {
- auto* profiler = SamplingHeapProfiler::Get();
- WaitableEvent event;
- StartStopThread thread(&event);
- thread.Start();
- event.Wait();
- RunStartStopLoop(profiler);
- thread.Join();
- EXPECT_EQ(0, GetRunningSessionsCount());
- }
- TEST_F(SamplingHeapProfilerTest, HookedAllocatorMuted) {
- ScopedSuppressRandomnessForTesting suppress;
- EXPECT_FALSE(PoissonAllocationSampler::AreHookedSamplesMuted());
- auto* sampler = PoissonAllocationSampler::Get();
- sampler->SetSamplingInterval(1024);
- {
- PoissonAllocationSampler::ScopedMuteHookedSamplesForTesting mute_hooks;
- EXPECT_TRUE(PoissonAllocationSampler::AreHookedSamplesMuted());
- SamplesCollector collector(10000);
- // A ScopedMuteHookedSamplesForTesting exists so hooked allocations should
- // be ignored.
- sampler->AddSamplesObserver(&collector);
- void* volatile p = malloc(10000);
- free(p);
- sampler->RemoveSamplesObserver(&collector);
- EXPECT_FALSE(collector.sample_added);
- EXPECT_FALSE(collector.sample_removed);
- // Manual allocations should be captured.
- sampler->AddSamplesObserver(&collector);
- void* const kAddress = reinterpret_cast<void*>(0x1234);
- sampler->RecordAlloc(kAddress, 10000,
- PoissonAllocationSampler::kManualForTesting, nullptr);
- sampler->RecordFree(kAddress);
- sampler->RemoveSamplesObserver(&collector);
- EXPECT_TRUE(collector.sample_added);
- EXPECT_TRUE(collector.sample_removed);
- }
- EXPECT_FALSE(PoissonAllocationSampler::AreHookedSamplesMuted());
- // Hooked allocations should be captured again.
- SamplesCollector collector(10000);
- sampler->AddSamplesObserver(&collector);
- void* volatile p = malloc(10000);
- free(p);
- sampler->RemoveSamplesObserver(&collector);
- EXPECT_TRUE(collector.sample_added);
- EXPECT_TRUE(collector.sample_removed);
- }
- } // namespace base
|