blob: 3d7e99a3261b693d2e6be20216279f9b6bd869d3 [file] [log] [blame]
// 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 "components/services/heap_profiling/public/cpp/sampling_profiler_wrapper.h"
#include <unordered_set>
#include <utility>
#include "base/debug/stack_trace.h"
#include "base/lazy_instance.h"
#include "base/sampling_heap_profiler/sampling_heap_profiler.h"
#include "base/synchronization/lock.h"
#include "base/trace_event/heap_profiler_event_filter.h"
#include "base/trace_event/memory_dump_manager.h"
using base::trace_event::AllocationContext;
using base::trace_event::AllocationContextTracker;
using CaptureMode = base::trace_event::AllocationContextTracker::CaptureMode;
namespace heap_profiling {
namespace {
bool g_initialized_ = false;
base::LazyInstance<base::Lock>::Leaky g_on_init_allocator_shim_lock_;
base::LazyInstance<base::OnceClosure>::Leaky g_on_init_allocator_shim_callback_;
base::LazyInstance<scoped_refptr<base::TaskRunner>>::Leaky
g_on_init_allocator_shim_task_runner_;
// In NATIVE stack mode, whether to insert stack names into the backtraces.
bool g_include_thread_names = false;
} // namespace
void InitTLSSlot() {
base::SamplingHeapProfiler::Init();
}
// In order for pseudo stacks to work, trace event filtering must be enabled.
void EnableTraceEventFiltering() {
std::string filter_string = base::JoinString(
{"*", TRACE_DISABLED_BY_DEFAULT("net"), TRACE_DISABLED_BY_DEFAULT("cc"),
base::trace_event::MemoryDumpManager::kTraceCategory},
",");
base::trace_event::TraceConfigCategoryFilter category_filter;
category_filter.InitializeFromString(filter_string);
base::trace_event::TraceConfig::EventFilterConfig heap_profiler_filter_config(
base::trace_event::HeapProfilerEventFilter::kName);
heap_profiler_filter_config.SetCategoryFilter(category_filter);
base::trace_event::TraceConfig::EventFilters filters;
filters.push_back(heap_profiler_filter_config);
base::trace_event::TraceConfig filtering_trace_config;
filtering_trace_config.SetEventFilters(filters);
base::trace_event::TraceLog::GetInstance()->SetEnabled(
filtering_trace_config, base::trace_event::TraceLog::FILTERING_MODE);
}
void InitAllocationRecorder(mojom::ProfilingParamsPtr params) {
// Must be done before hooking any functions that make stack traces.
base::debug::EnableInProcessStackDumping();
if (params->stack_mode == mojom::StackMode::NATIVE_WITH_THREAD_NAMES) {
g_include_thread_names = true;
base::SamplingHeapProfiler::Get()->SetRecordThreadNames(true);
}
switch (params->stack_mode) {
case mojom::StackMode::PSEUDO:
EnableTraceEventFiltering();
AllocationContextTracker::SetCaptureMode(CaptureMode::PSEUDO_STACK);
break;
case mojom::StackMode::MIXED:
EnableTraceEventFiltering();
AllocationContextTracker::SetCaptureMode(CaptureMode::MIXED_STACK);
break;
case mojom::StackMode::NATIVE_WITH_THREAD_NAMES:
case mojom::StackMode::NATIVE_WITHOUT_THREAD_NAMES:
// This would track task contexts only.
AllocationContextTracker::SetCaptureMode(CaptureMode::NATIVE_STACK);
break;
}
}
namespace {
// Notifies the test clients that allocation hooks have been initialized.
void AllocatorHooksHaveBeenInitialized() {
base::AutoLock lock(g_on_init_allocator_shim_lock_.Get());
g_initialized_ = true;
if (!g_on_init_allocator_shim_callback_.Get())
return;
g_on_init_allocator_shim_task_runner_.Get()->PostTask(
FROM_HERE, std::move(*g_on_init_allocator_shim_callback_.Pointer()));
}
mojom::AllocatorType ConvertType(
base::PoissonAllocationSampler::AllocatorType type) {
switch (type) {
case base::PoissonAllocationSampler::AllocatorType::kMalloc:
return mojom::AllocatorType::kMalloc;
case base::PoissonAllocationSampler::AllocatorType::kPartitionAlloc:
return mojom::AllocatorType::kPartitionAlloc;
case base::PoissonAllocationSampler::AllocatorType::kBlinkGC:
return mojom::AllocatorType::kOilpan;
}
}
} // namespace
bool SetOnInitAllocatorShimCallbackForTesting(
base::OnceClosure callback,
scoped_refptr<base::TaskRunner> task_runner) {
base::AutoLock lock(g_on_init_allocator_shim_lock_.Get());
if (g_initialized_)
return true;
g_on_init_allocator_shim_callback_.Get() = std::move(callback);
g_on_init_allocator_shim_task_runner_.Get() = task_runner;
return false;
}
void SamplingProfilerWrapper::StartProfiling(mojom::ProfilingParamsPtr params) {
size_t sampling_rate = params->sampling_rate;
InitAllocationRecorder(std::move(params));
auto* profiler = base::SamplingHeapProfiler::Get();
profiler->SetSamplingInterval(sampling_rate);
profiler->Start();
AllocatorHooksHaveBeenInitialized();
}
void SamplingProfilerWrapper::StopProfiling() {
base::SamplingHeapProfiler::Get()->Stop();
}
mojom::HeapProfilePtr SamplingProfilerWrapper::RetrieveHeapProfile() {
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->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*>(sample.stack.data() +
sample.stack.size()));
if (g_include_thread_names) {
mojo_sample->stack.push_back(
reinterpret_cast<uintptr_t>(sample.thread_name));
thread_names.insert(sample.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);
return profile;
}
} // namespace heap_profiling