#include "components/services/heap_profiling/connection_manager.h"
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback.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 {
struct ConnectionManager::DumpProcessesForTracingTracking
: public base::RefCountedThreadSafe<DumpProcessesForTracingTracking> {
size_t waiting_responses = 0;
DumpProcessesForTracingCallback callback;
VmRegions vm_regions;
std::vector<memory_instrumentation::mojom::HeapProfileResultPtr> results;
private:
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,
mojom::ProfilingService::AddProfilingClientCallback
started_profiling_callback)
: client(std::move(client)),
process_type(process_type),
stack_mode(stack_mode),
sampling_rate(sampling_rate),
started_profiling_callback(std::move(started_profiling_callback)) {
this->client.set_disconnect_handler(std::move(complete_cb));
}
bool HeapDumpNeedsVmRegions() {
return stack_mode == mojom::StackMode::NATIVE_WITHOUT_THREAD_NAMES ||
stack_mode == mojom::StackMode::NATIVE_WITH_THREAD_NAMES;
}
mojo::Remote<mojom::ProfilingClient> client;
mojom::ProcessType process_type;
mojom::StackMode stack_mode;
bool started_profiling = false;
uint32_t sampling_rate = 1;
mojom::ProfilingService::AddProfilingClientCallback
started_profiling_callback;
};
ConnectionManager::ConnectionManager() {
metrics_timer_.Start(FROM_HERE, base::Hours(24),
base::BindRepeating(&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,
mojom::ProfilingService::AddProfilingClientCallback
started_profiling_closure) {
base::AutoLock lock(connections_lock_);
if (connections_.find(pid) != connections_.end()) {
std::move(started_profiling_closure).Run(false);
return;
}
CompleteCallback complete_cb =
base::BindOnce(&ConnectionManager::OnConnectionComplete,
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,
std::move(started_profiling_closure));
connection->client->StartProfiling(
std::move(params), base::BindOnce(&ConnectionManager::OnProfilingStarted,
weak_factory_.GetWeakPtr(), pid));
connections_[pid] = std::move(connection);
}
std::vector<base::ProcessId> ConnectionManager::GetConnectionPids() {
base::AutoLock lock(connections_lock_);
std::vector<base::ProcessId> results;
results.reserve(connections_.size());
for (const auto& pair : connections_) {
if (pair.second->started_profiling)
results.push_back(pair.first);
}
return results;
}
std::vector<base::ProcessId>
ConnectionManager::GetConnectionPidsThatNeedVmRegions() {
base::AutoLock lock(connections_lock_);
std::vector<base::ProcessId> results;
results.reserve(connections_.size());
for (const auto& pair : connections_) {
if (pair.second->HeapDumpNeedsVmRegions())
results.push_back(pair.first);
}
return results;
}
void ConnectionManager::OnConnectionComplete(base::ProcessId pid) {
base::AutoLock lock(connections_lock_);
auto found = connections_.find(pid);
CHECK(found != connections_.end());
if (!found->second->started_profiling_callback.is_null()) {
std::move(found->second->started_profiling_callback).Run(false);
}
connections_.erase(found);
}
void ConnectionManager::OnProfilingStarted(base::ProcessId pid) {
base::AutoLock lock(connections_lock_);
auto found = connections_.find(pid);
if (found != connections_.end()) {
found->second->started_profiling = true;
std::move(found->second->started_profiling_callback).Run(true);
}
}
void ConnectionManager::ReportMetrics() {
base::AutoLock lock(connections_lock_);
for (auto& pair : connections_) {
UMA_HISTOGRAM_ENUMERATION("HeapProfiling.ProfiledProcess.Type",
pair.second->process_type,
static_cast<int>(mojom::ProcessType::LAST) + 1);
}
}
void ConnectionManager::DumpProcessesForTracing(
bool strip_path_from_mapped_files,
bool write_proto,
DumpProcessesForTracingCallback callback,
VmRegions vm_regions) {
base::AutoLock lock(connections_lock_);
if (connections_.empty()) {
std::move(callback).Run(
std::vector<memory_instrumentation::mojom::HeapProfileResultPtr>());
return;
}
auto tracking = base::MakeRefCounted<DumpProcessesForTracingTracking>();
tracking->waiting_responses = connections_.size();
tracking->callback = std::move(callback);
tracking->vm_regions = std::move(vm_regions);
tracking->results.reserve(connections_.size());
for (auto& it : connections_) {
base::ProcessId pid = it.first;
Connection* connection = it.second.get();
connection->client->RetrieveHeapProfile(base::BindOnce(
&ConnectionManager::HeapProfileRetrieved, weak_factory_.GetWeakPtr(),
tracking, pid, connection->process_type, strip_path_from_mapped_files,
connection->sampling_rate));
}
}
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;
std::string escaped_context;
base::EscapeJSONString(context, false ,
&escaped_context);
context_id = context_map
.emplace(std::move(escaped_context),
static_cast<int>(context_map.size() + 1))
.first->second;
}
size_t alloc_size = sample->total;
float alloc_count = 1;
if (sample->size != 0)
alloc_count = float(sample->total) / float(sample->size);
std::vector<Address> stack(sample->stack.begin(), sample->stack.end());
AllocationMetrics& metrics =
allocs
.emplace(std::piecewise_construct,
std::forward_as_tuple(sample->allocator, std::move(stack),
context_id),
std::forward_as_tuple())
.first->second;
metrics.size += alloc_size;
metrics.count += alloc_count;
}
for (const auto& str : profile->strings) {
std::string quoted_string;
base::EscapeJSONString(str.second, false ,
"ed_string);
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) {
DCHECK(tracking->waiting_responses > 0);
ExportParams params;
bool success =
ConvertProfileToExportParams(std::move(profile), sampling_rate, ¶ms);
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 =
memory_instrumentation::mojom::HeapProfileResult::New();
result->pid = pid;
result->json = ExportMemoryMapsAndV2StackTraceToJSON(¶ms);
tracking->results.push_back(std::move(result));
next_id_ = params.next_id;
}
if (--tracking->waiting_responses == 0)
std::move(tracking->callback).Run(std::move(tracking->results));
}
}