blob: 3a0c23cafff674d05c689e5eee071b7a2728b6d6 [file] [log] [blame]
// Copyright 2017 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/connection_manager.h"
#include "base/bind.h"
#include "base/json/string_escape.h"
#include "base/metrics/histogram_macros.h"
#include "components/services/heap_profiling/json_exporter.h"
#include "components/services/heap_profiling/public/cpp/profiling_client.h"
namespace heap_profiling {
// Tracking information for DumpProcessForTracing(). This struct is
// refcounted since there will be many background thread calls (one for each
// AllocationTracker) and the callback is only issued when each has
// responded.
// This class is not threadsafe, its members must only be accessed on the
// I/O thread.
struct ConnectionManager::DumpProcessesForTracingTracking
: public base::RefCountedThreadSafe<DumpProcessesForTracingTracking> {
// Number of processes we're still waiting on responses for. When this gets
// to 0, the callback will be issued.
size_t waiting_responses = 0;
// Callback to issue when dumps are complete.
DumpProcessesForTracingCallback callback;
// Info about the request.
VmRegions vm_regions;
// Collects the results.
std::vector<memory_instrumentation::mojom::HeapProfileResultPtr> results;
friend class base::RefCountedThreadSafe<DumpProcessesForTracingTracking>;
virtual ~DumpProcessesForTracingTracking() = default;
struct ConnectionManager::Connection {
Connection(CompleteCallback complete_cb,
mojo::PendingRemote<mojom::ProfilingClient> client,
mojom::ProcessType process_type,
uint32_t sampling_rate,
mojom::StackMode stack_mode)
: client(std::move(client)),
sampling_rate(sampling_rate) {
bool HeapDumpNeedsVmRegions() {
return stack_mode == mojom::StackMode::NATIVE_WITHOUT_THREAD_NAMES ||
stack_mode == mojom::StackMode::NATIVE_WITH_THREAD_NAMES ||
stack_mode == mojom::StackMode::MIXED;
mojo::Remote<mojom::ProfilingClient> client;
mojom::ProcessType process_type;
mojom::StackMode stack_mode;
// When sampling is enabled, allocations are recorded with probability (size /
// sampling_rate) when size < sampling_rate. When size >= sampling_rate, the
// aggregate probability of an allocation being recorded is 1.0, but the math
// and details are tricky. See
// A |sampling_rate| of 1 is equivalent to recording all allocations.
uint32_t sampling_rate = 1;
ConnectionManager::ConnectionManager() {
FROM_HERE, base::TimeDelta::FromHours(24),
base::Bind(&ConnectionManager::ReportMetrics, base::Unretained(this)));
ConnectionManager::~ConnectionManager() = default;
void ConnectionManager::OnNewConnection(
base::ProcessId pid,
mojo::PendingRemote<mojom::ProfilingClient> client,
mojom::ProcessType process_type,
mojom::ProfilingParamsPtr params) {
base::AutoLock lock(connections_lock_);
// Attempting to start profiling on an already profiled processs should have
// no effect.
if (connections_.find(pid) != connections_.end())
// It's theoretically possible that we started profiling a process, the
// profiling was stopped [e.g. by hitting the 10-s timeout], and then we tried
// to start profiling again. The ProfilingClient will refuse to start again.
// But the ConnectionManager will not be able to distinguish this
// never-started ProfilingClient from a brand new ProfilingClient that happens
// to share the same pid. This is a rare condition which should only happen
// when the user is attempting to manually start profiling for processes, so
// we ignore this edge case.
CompleteCallback complete_cb =
weak_factory_.GetWeakPtr(), pid);
auto connection = std::make_unique<Connection>(
std::move(complete_cb), std::move(client), process_type,
params->sampling_rate, params->stack_mode);
connections_[pid] = std::move(connection);
std::vector<base::ProcessId> ConnectionManager::GetConnectionPids() {
base::AutoLock lock(connections_lock_);
std::vector<base::ProcessId> results;
for (const auto& pair : connections_)
return results;
ConnectionManager::GetConnectionPidsThatNeedVmRegions() {
base::AutoLock lock(connections_lock_);
std::vector<base::ProcessId> results;
for (const auto& pair : connections_) {
if (pair.second->HeapDumpNeedsVmRegions())
return results;
void ConnectionManager::OnConnectionComplete(base::ProcessId pid) {
base::AutoLock lock(connections_lock_);
auto found = connections_.find(pid);
CHECK(found != connections_.end());
void ConnectionManager::ReportMetrics() {
base::AutoLock lock(connections_lock_);
for (auto& pair : connections_) {
static_cast<int>(mojom::ProcessType::LAST) + 1);
void ConnectionManager::DumpProcessesForTracing(
bool strip_path_from_mapped_files,
DumpProcessesForTracingCallback callback,
VmRegions vm_regions) {
base::AutoLock lock(connections_lock_);
// Early out if there are no connections.
if (connections_.empty()) {
auto tracking = base::MakeRefCounted<DumpProcessesForTracingTracking>();
tracking->waiting_responses = connections_.size();
tracking->callback = std::move(callback);
tracking->vm_regions = std::move(vm_regions);
for (auto& it : connections_) {
base::ProcessId pid = it.first;
Connection* connection = it.second.get();
&ConnectionManager::HeapProfileRetrieved, weak_factory_.GetWeakPtr(),
tracking, pid, connection->process_type, strip_path_from_mapped_files,
bool ConnectionManager::ConvertProfileToExportParams(
mojom::HeapProfilePtr profile,
uint32_t sampling_rate,
ExportParams* params) {
AllocationMap allocs;
ContextMap context_map;
AddressToStringMap string_map;
for (const mojom::HeapProfileSamplePtr& sample : profile->samples) {
int context_id = 0;
if (sample->context_id) {
auto it = profile->strings.find(sample->context_id);
if (it == profile->strings.end())
return false;
const std::string& context = it->second;
// Escape the strings early, to simplify exporting a heap dump.
std::string escaped_context;
base::EscapeJSONString(context, false /* put_in_quotes */,
context_id = context_map
static_cast<int>(context_map.size() + 1))
size_t alloc_size = sample->size;
size_t alloc_count = 1;
// If allocations were sampled, then we need to desample to return accurate
// results.
// TODO(alph): Move it closer to the the sampler, so other components
// wouldn't care about the math.
if (alloc_size < sampling_rate && alloc_size != 0) {
// To desample, we need to know the probability P that an allocation will
// be sampled. Once we know P, we still have to deal with discretization.
// Let's say that there's 1 allocation with P=0.85. Should we report 1 or
// 2 allocations? Should we report a fudged size (size / 0.85), or a
// discreted size, e.g. (1 * size) or (2 * size)? There are tradeoffs.
// We choose to emit a fudged size, which will return a more accurate
// total allocation size, but less accurate per-allocation size.
// The aggregate probability that an allocation will be sampled is
// alloc_size / sampling_rate. For a more detailed treatise, see
float desampling_multiplier =
static_cast<float>(sampling_rate) / static_cast<float>(alloc_size);
alloc_count *= desampling_multiplier;
alloc_size *= desampling_multiplier;
std::vector<Address> stack(sample->stack.begin(), sample->stack.end());
AllocationMetrics& metrics =
std::forward_as_tuple(sample->allocator, std::move(stack),
metrics.size += alloc_size;
metrics.count += alloc_count;
for (const auto& str : profile->strings) {
std::string quoted_string;
// Escape the strings before saving them, to simplify exporting a heap dump.
base::EscapeJSONString(str.second, false /* put_in_quotes */,
string_map.emplace(str.first, std::move(quoted_string));
params->allocs = std::move(allocs);
params->context_map = std::move(context_map);
params->mapped_strings = std::move(string_map);
return true;
void ConnectionManager::HeapProfileRetrieved(
scoped_refptr<DumpProcessesForTracingTracking> tracking,
base::ProcessId pid,
mojom::ProcessType process_type,
bool strip_path_from_mapped_files,
uint32_t sampling_rate,
mojom::HeapProfilePtr profile) {
// All code paths through here must issue the callback when waiting_responses
// is 0 or the browser will wait forever for the dump.
DCHECK(tracking->waiting_responses > 0);
ExportParams params;
bool success =
ConvertProfileToExportParams(std::move(profile), sampling_rate, &params);
if (success) {
params.process_type = process_type;
params.strip_path_from_mapped_files = strip_path_from_mapped_files;
params.next_id = next_id_;
auto it = tracking->vm_regions.find(pid);
if (it != tracking->vm_regions.end())
params.maps = std::move(it->second);
memory_instrumentation::mojom::HeapProfileResultPtr result =
result->pid = pid;
result->json = ExportMemoryMapsAndV2StackTraceToJSON(&params);
next_id_ = params.next_id;
// When all responses complete, issue done callback.
if (--tracking->waiting_responses == 0)
} // namespace heap_profiling