|  | // 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/threading/simple_thread.h" | 
|  | #include "build/build_config.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | namespace base { | 
|  | namespace { | 
|  |  | 
|  | class SamplingHeapProfilerTest : public ::testing::Test { | 
|  | #if defined(OS_MACOSX) | 
|  | void SetUp() override { allocator::InitializeAllocatorShim(); } | 
|  | #endif | 
|  | }; | 
|  |  | 
|  | class SamplesCollector : public SamplingHeapProfiler::SamplesObserver { | 
|  | public: | 
|  | explicit SamplesCollector(size_t watch_size) : watch_size_(watch_size) {} | 
|  |  | 
|  | void SampleAdded(uint32_t id, size_t size, size_t) override { | 
|  | if (sample_added || size != watch_size_) | 
|  | return; | 
|  | sample_id_ = id; | 
|  | sample_added = true; | 
|  | } | 
|  |  | 
|  | void SampleRemoved(uint32_t id) override { | 
|  | if (id == sample_id_) | 
|  | sample_removed = true; | 
|  | } | 
|  |  | 
|  | bool sample_added = false; | 
|  | bool sample_removed = false; | 
|  |  | 
|  | private: | 
|  | size_t watch_size_; | 
|  | uint32_t sample_id_ = 0; | 
|  | }; | 
|  |  | 
|  | TEST_F(SamplingHeapProfilerTest, CollectSamples) { | 
|  | SamplingHeapProfiler::InitTLSSlot(); | 
|  | SamplesCollector collector(10000); | 
|  | SamplingHeapProfiler* profiler = SamplingHeapProfiler::GetInstance(); | 
|  | profiler->SuppressRandomnessForTest(true); | 
|  | profiler->SetSamplingInterval(1024); | 
|  | profiler->Start(); | 
|  | profiler->AddSamplesObserver(&collector); | 
|  | void* volatile p = malloc(10000); | 
|  | free(p); | 
|  | profiler->Stop(); | 
|  | profiler->RemoveSamplesObserver(&collector); | 
|  | CHECK(collector.sample_added); | 
|  | CHECK(collector.sample_removed); | 
|  | } | 
|  |  | 
|  | const int kNumberOfAllocations = 10000; | 
|  |  | 
|  | 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)()) { | 
|  | SamplingHeapProfiler::InitTLSSlot(); | 
|  | SamplingHeapProfiler* profiler = SamplingHeapProfiler::GetInstance(); | 
|  | profiler->SuppressRandomnessForTest(false); | 
|  | 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([]() { | 
|  | SimpleThread* t1 = new MyThread1(); | 
|  | SimpleThread* t2 = new MyThread2(); | 
|  | 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(); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace base |