blob: b45b446977a19ff4cf2c06f4fe8ef139fda37ed6 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/services/heap_profiling/public/cpp/profiling_client.h"
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>
#include "base/compiler_specific.h"
#include "base/debug/stack_trace.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/sampling_heap_profiler/poisson_allocation_sampler.h"
#include "base/sampling_heap_profiler/sampling_heap_profiler.h"
#include "base/trace_event/heap_profiler_allocation_context_tracker.h"
#include "base/trace_event/malloc_dump_provider.h"
#include "base/trace_event/memory_dump_manager.h"
#include "build/build_config.h"
#include "partition_alloc/buildflags.h"
#if BUILDFLAG(IS_APPLE) && !PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && \
PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
#include "partition_alloc/shim/allocator_interception_apple.h"
#endif // BUILDFLAG(IS_APPLE) && !PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
// && PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
using base::allocator::dispatcher::AllocationSubsystem;
namespace heap_profiling {
ProfilingClient::ProfilingClient() = default;
ProfilingClient::~ProfilingClient() = default;
void ProfilingClient::BindToInterface(
mojo::PendingReceiver<mojom::ProfilingClient> receiver) {
receivers_.Add(this, std::move(receiver));
}
#if BUILDFLAG(IS_APPLE) && !PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && \
PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
void ShimNewMallocZonesAndReschedule(base::Time end_time,
base::TimeDelta delay) {
allocator_shim::ShimNewMallocZones();
if (base::Time::Now() > end_time) {
return;
}
base::TimeDelta next_delay = delay * 2;
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&ShimNewMallocZonesAndReschedule, end_time, next_delay),
delay);
}
#endif // BUILDFLAG(IS_APPLE) && !PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
// && BULDFLAG(USE_ALLOCATOR_SHIM)
void ProfilingClient::StartProfiling(mojom::ProfilingParamsPtr params,
StartProfilingCallback callback) {
if (started_profiling_)
return;
started_profiling_ = true;
#if BUILDFLAG(IS_APPLE) && !PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) && \
PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
// On macOS, this call is necessary to shim malloc zones that were created
// after startup. This cannot be done during shim initialization because the
// task scheduler has not yet been initialized.
//
// Wth PartitionAlloc, the shims are already in place, calling this leads to
// an infinite loop.
base::Time end_time = base::Time::Now() + base::Minutes(1);
base::TimeDelta initial_delay = base::Seconds(1);
ShimNewMallocZonesAndReschedule(end_time, initial_delay);
#endif // BUILDFLAG(IS_APPLE) && !PA_BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
// && PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
StartProfilingInternal(std::move(params), std::move(callback));
}
namespace {
bool g_initialized_ = false;
base::Lock& GetOnInitAllocatorShimLock() {
static base::NoDestructor<base::Lock> instance;
return *instance;
}
base::OnceClosure& GetOnInitAllocatorShimCallback() {
static base::NoDestructor<base::OnceClosure> instance;
return *instance;
}
scoped_refptr<base::TaskRunner>& GetOnInitAllocatorShimTaskRunner() {
static base::NoDestructor<scoped_refptr<base::TaskRunner>> instance;
return *instance;
}
// In NATIVE stack mode, whether to insert stack names into the backtraces.
bool g_include_thread_names = false;
void InitAllocationRecorder(mojom::ProfilingParamsPtr params) {
using base::trace_event::AllocationContextTracker;
using CaptureMode = base::trace_event::AllocationContextTracker::CaptureMode;
#if !BUILDFLAG(IS_WIN) || !defined(OFFICIAL_BUILD)
// Must be done before hooking any functions that make stack traces. Windows
// release builds crash if symbols are requested after sandbox lockdown, but
// will still produce address-only stacks if this function not called.
base::debug::EnableInProcessStackDumping();
#endif
if (params->stack_mode == mojom::StackMode::NATIVE_WITH_THREAD_NAMES) {
g_include_thread_names = true;
base::SamplingHeapProfiler::Get()->EnableRecordThreadNames();
}
switch (params->stack_mode) {
case mojom::StackMode::NATIVE_WITH_THREAD_NAMES:
case mojom::StackMode::NATIVE_WITHOUT_THREAD_NAMES:
// This would track task contexts only.
AllocationContextTracker::SetCaptureMode(CaptureMode::kNativeStack);
break;
}
}
// Notifies the test clients that allocation hooks have been initialized.
void AllocatorHooksHaveBeenInitialized() {
base::AutoLock lock(GetOnInitAllocatorShimLock());
g_initialized_ = true;
if (!GetOnInitAllocatorShimCallback())
return;
GetOnInitAllocatorShimTaskRunner()->PostTask(
FROM_HERE, std::move(GetOnInitAllocatorShimCallback()));
}
mojom::AllocatorType ConvertType(AllocationSubsystem type) {
switch (type) {
case AllocationSubsystem::kAllocatorShim:
return mojom::AllocatorType::kMalloc;
case AllocationSubsystem::kPartitionAllocator:
return mojom::AllocatorType::kPartitionAlloc;
case AllocationSubsystem::kManualForTesting:
NOTREACHED();
}
}
} // namespace
void InitTLSSlot() {
base::SamplingHeapProfiler::Init();
}
bool SetOnInitAllocatorShimCallbackForTesting(
base::OnceClosure callback,
scoped_refptr<base::TaskRunner> task_runner) {
base::AutoLock lock(GetOnInitAllocatorShimLock());
if (g_initialized_)
return true;
GetOnInitAllocatorShimCallback() = std::move(callback);
GetOnInitAllocatorShimTaskRunner() = task_runner;
return false;
}
void ProfilingClient::StartProfilingInternal(mojom::ProfilingParamsPtr params,
StartProfilingCallback callback) {
size_t sampling_rate = params->sampling_rate;
InitAllocationRecorder(std::move(params));
auto* profiler = base::SamplingHeapProfiler::Get();
profiler->SetSamplingInterval(sampling_rate);
profiler->Start();
AllocatorHooksHaveBeenInitialized();
std::move(callback).Run();
}
void ProfilingClient::RetrieveHeapProfile(
RetrieveHeapProfileCallback callback) {
auto* profiler = base::SamplingHeapProfiler::Get();
std::vector<base::SamplingHeapProfiler::Sample> samples =
profiler->GetSamples(/*profile_id=*/0);
// It's important to retrieve strings after samples, as otherwise it could
// miss a string referenced by a sample.
std::vector<const char*> strings = profiler->GetStrings();
mojom::HeapProfilePtr profile = mojom::HeapProfile::New();
profile->samples.reserve(samples.size());
std::unordered_set<const char*> thread_names;
for (const auto& sample : samples) {
auto mojo_sample = mojom::HeapProfileSample::New();
mojo_sample->allocator = ConvertType(sample.allocator);
mojo_sample->size = sample.size;
mojo_sample->total = sample.total;
mojo_sample->context_id = reinterpret_cast<uintptr_t>(sample.context);
mojo_sample->stack.reserve(sample.stack.size() +
(g_include_thread_names ? 1 : 0));
mojo_sample->stack.insert(
mojo_sample->stack.end(),
reinterpret_cast<const uintptr_t*>(sample.stack.data()),
reinterpret_cast<const uintptr_t*>(
UNSAFE_TODO(sample.stack.data() + sample.stack.size())));
if (g_include_thread_names) {
static const char* kUnknownThreadName = "<unknown>";
const char* thread_name =
sample.thread_name ? sample.thread_name : kUnknownThreadName;
mojo_sample->stack.push_back(reinterpret_cast<uintptr_t>(thread_name));
thread_names.insert(thread_name);
}
profile->samples.push_back(std::move(mojo_sample));
}
profile->strings.reserve(strings.size() + thread_names.size());
for (const char* string : strings)
profile->strings.emplace(reinterpret_cast<uintptr_t>(string), string);
for (const char* string : thread_names)
profile->strings.emplace(reinterpret_cast<uintptr_t>(string), string);
std::move(callback).Run(std::move(profile));
}
} // namespace heap_profiling