blob: a158a4536871bb75abbb086df23ec4ffdbb0ceb8 [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 "services/resource_coordinator/memory_instrumentation/coordinator_impl.h"
#include <inttypes.h>
#include <stdio.h>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/histogram_macros.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/trace_event/memory_dump_manager.h"
#include "base/trace_event/memory_dump_request_args.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "services/resource_coordinator/memory_instrumentation/queued_request_dispatcher.h"
#include "services/resource_coordinator/memory_instrumentation/switches.h"
#include "services/resource_coordinator/public/cpp/memory_instrumentation/client_process_impl.h"
#include "services/resource_coordinator/public/mojom/memory_instrumentation/constants.mojom.h"
#include "services/resource_coordinator/public/mojom/memory_instrumentation/memory_instrumentation.mojom.h"
#include "services/service_manager/public/cpp/identity.h"
#if defined(OS_MACOSX) && !defined(OS_IOS)
#include "base/mac/mac_util.h"
#endif
using base::trace_event::MemoryDumpLevelOfDetail;
using base::trace_event::MemoryDumpType;
namespace memory_instrumentation {
namespace {
memory_instrumentation::CoordinatorImpl* g_coordinator_impl;
constexpr base::TimeDelta kHeapDumpTimeout = base::TimeDelta::FromSeconds(60);
// A wrapper classes that allows a string to be exported as JSON in a trace
// event.
class StringWrapper : public base::trace_event::ConvertableToTraceFormat {
public:
explicit StringWrapper(std::string&& json) : json_(std::move(json)) {}
void AppendAsTraceFormat(std::string* out) const override {
out->append(json_);
}
std::string json_;
};
} // namespace
// static
CoordinatorImpl* CoordinatorImpl::GetInstance() {
return g_coordinator_impl;
}
CoordinatorImpl::CoordinatorImpl(service_manager::Connector* connector)
: next_dump_id_(0),
client_process_timeout_(base::TimeDelta::FromSeconds(15)),
weak_ptr_factory_(this) {
process_map_ = std::make_unique<ProcessMap>(connector);
DCHECK(!g_coordinator_impl);
g_coordinator_impl = this;
base::trace_event::MemoryDumpManager::GetInstance()->set_tracing_process_id(
mojom::kServiceTracingProcessId);
tracing_observer_ = std::make_unique<TracingObserver>(
base::trace_event::TraceLog::GetInstance(), nullptr);
}
CoordinatorImpl::~CoordinatorImpl() {
g_coordinator_impl = nullptr;
}
base::ProcessId CoordinatorImpl::GetProcessIdForClientIdentity(
service_manager::Identity identity) const {
DCHECK(identity.IsValid());
return process_map_->GetProcessId(identity);
}
std::map<base::ProcessId, std::vector<std::string>>
CoordinatorImpl::ComputePidToServiceNamesMap() const {
return process_map_->ComputePidToServiceNamesMap();
}
service_manager::Identity CoordinatorImpl::GetClientIdentityForCurrentRequest()
const {
return bindings_.dispatch_context();
}
void CoordinatorImpl::BindCoordinatorRequest(
mojom::CoordinatorRequest request,
const service_manager::BindSourceInfo& source_info) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
bindings_.AddBinding(this, std::move(request), source_info.identity);
}
void CoordinatorImpl::BindHeapProfilerHelperRequest(
mojom::HeapProfilerHelperRequest request,
const service_manager::BindSourceInfo& source_info) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
bindings_heap_profiler_helper_.AddBinding(this, std::move(request),
source_info.identity);
}
void CoordinatorImpl::RequestGlobalMemoryDump(
MemoryDumpType dump_type,
MemoryDumpLevelOfDetail level_of_detail,
const std::vector<std::string>& allocator_dump_names,
RequestGlobalMemoryDumpCallback callback) {
// This merely strips out the |dump_guid| argument.
auto adapter = [](RequestGlobalMemoryDumpCallback callback, bool success,
uint64_t, mojom::GlobalMemoryDumpPtr global_memory_dump) {
std::move(callback).Run(success, std::move(global_memory_dump));
};
QueuedRequest::Args args(dump_type, level_of_detail, allocator_dump_names,
false /* add_to_trace */, base::kNullProcessId,
/*memory_footprint_only=*/false);
RequestGlobalMemoryDumpInternal(args,
base::BindOnce(adapter, std::move(callback)));
}
void CoordinatorImpl::RequestGlobalMemoryDumpForPid(
base::ProcessId pid,
const std::vector<std::string>& allocator_dump_names,
RequestGlobalMemoryDumpForPidCallback callback) {
// Error out early if process id is null to avoid confusing with global
// dump for all processes case when pid is kNullProcessId.
if (pid == base::kNullProcessId) {
std::move(callback).Run(false, nullptr);
return;
}
// This merely strips out the |dump_guid| argument; this is not relevant
// as we are not adding to trace.
auto adapter = [](RequestGlobalMemoryDumpForPidCallback callback,
bool success, uint64_t,
mojom::GlobalMemoryDumpPtr global_memory_dump) {
std::move(callback).Run(success, std::move(global_memory_dump));
};
QueuedRequest::Args args(
base::trace_event::MemoryDumpType::SUMMARY_ONLY,
base::trace_event::MemoryDumpLevelOfDetail::BACKGROUND,
allocator_dump_names, false /* add_to_trace */, pid,
/*memory_footprint_only=*/false);
RequestGlobalMemoryDumpInternal(args,
base::BindOnce(adapter, std::move(callback)));
}
void CoordinatorImpl::RequestPrivateMemoryFootprint(
base::ProcessId pid,
RequestPrivateMemoryFootprintCallback callback) {
// This merely strips out the |dump_guid| argument; this is not relevant
// as we are not adding to trace.
auto adapter = [](RequestPrivateMemoryFootprintCallback callback,
bool success, uint64_t,
mojom::GlobalMemoryDumpPtr global_memory_dump) {
std::move(callback).Run(success, std::move(global_memory_dump));
};
QueuedRequest::Args args(
base::trace_event::MemoryDumpType::SUMMARY_ONLY,
base::trace_event::MemoryDumpLevelOfDetail::BACKGROUND, {},
false /* add_to_trace */, pid, /*memory_footprint_only=*/true);
RequestGlobalMemoryDumpInternal(args,
base::BindOnce(adapter, std::move(callback)));
}
void CoordinatorImpl::RequestGlobalMemoryDumpAndAppendToTrace(
MemoryDumpType dump_type,
MemoryDumpLevelOfDetail level_of_detail,
RequestGlobalMemoryDumpAndAppendToTraceCallback callback) {
// This merely strips out the |dump_ptr| argument.
auto adapter = [](RequestGlobalMemoryDumpAndAppendToTraceCallback callback,
bool success, uint64_t dump_guid,
mojom::GlobalMemoryDumpPtr) {
std::move(callback).Run(success, dump_guid);
};
QueuedRequest::Args args(dump_type, level_of_detail, {},
true /* add_to_trace */, base::kNullProcessId,
/*memory_footprint_only=*/false);
RequestGlobalMemoryDumpInternal(args,
base::BindOnce(adapter, std::move(callback)));
}
void CoordinatorImpl::RegisterHeapProfiler(
mojom::HeapProfilerPtr heap_profiler) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
heap_profiler_ = std::move(heap_profiler);
}
void CoordinatorImpl::GetVmRegionsForHeapProfiler(
const std::vector<base::ProcessId>& pids,
GetVmRegionsForHeapProfilerCallback callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
uint64_t dump_guid = ++next_dump_id_;
std::unique_ptr<QueuedVmRegionRequest> request =
std::make_unique<QueuedVmRegionRequest>(dump_guid, std::move(callback));
in_progress_vm_region_requests_[dump_guid] = std::move(request);
auto names_for_pid = ComputePidToServiceNamesMap();
std::vector<QueuedRequestDispatcher::ClientInfo> clients;
for (const auto& kv : clients_) {
auto client_identity = kv.second->identity;
const base::ProcessId pid = GetProcessIdForClientIdentity(client_identity);
clients.emplace_back(kv.second->client.get(), pid, kv.second->process_type,
std::move(names_for_pid[pid]));
}
QueuedVmRegionRequest* request_ptr =
in_progress_vm_region_requests_[dump_guid].get();
auto os_callback =
base::BindRepeating(&CoordinatorImpl::OnOSMemoryDumpForVMRegions,
weak_ptr_factory_.GetWeakPtr(), dump_guid);
QueuedRequestDispatcher::SetUpAndDispatchVmRegionRequest(request_ptr, clients,
pids, os_callback);
FinalizeVmRegionDumpIfAllManagersReplied(dump_guid);
}
void CoordinatorImpl::RegisterClientProcess(
mojom::ClientProcessPtr client_process_ptr,
mojom::ProcessType process_type) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
mojom::ClientProcess* client_process = client_process_ptr.get();
client_process_ptr.set_connection_error_handler(
base::BindOnce(&CoordinatorImpl::UnregisterClientProcess,
weak_ptr_factory_.GetWeakPtr(), client_process));
auto identity = GetClientIdentityForCurrentRequest();
auto client_info = std::make_unique<ClientInfo>(
std::move(identity), std::move(client_process_ptr), process_type);
auto iterator_and_inserted =
clients_.emplace(client_process, std::move(client_info));
DCHECK(iterator_and_inserted.second);
}
void CoordinatorImpl::UnregisterClientProcess(
mojom::ClientProcess* client_process) {
QueuedRequest* request = GetCurrentRequest();
if (request != nullptr) {
// Check if we are waiting for an ack from this client process.
auto it = request->pending_responses.begin();
while (it != request->pending_responses.end()) {
// The calls to On*MemoryDumpResponse below, if executed, will delete the
// element under the iterator which invalidates it. To avoid this we
// increment the iterator in advance while keeping a reference to the
// current element.
std::set<QueuedRequest::PendingResponse>::iterator current = it++;
if (current->client != client_process)
continue;
RemovePendingResponse(client_process, current->type);
request->failed_memory_dump_count++;
}
FinalizeGlobalMemoryDumpIfAllManagersReplied();
}
for (auto& pair : in_progress_vm_region_requests_) {
QueuedVmRegionRequest* request = pair.second.get();
auto it = request->pending_responses.begin();
while (it != request->pending_responses.end()) {
auto current = it++;
if (*current == client_process) {
request->pending_responses.erase(current);
}
}
}
// Try to finalize all outstanding vm region requests.
for (auto& pair : in_progress_vm_region_requests_) {
// PostTask to avoid re-entrancy or modification of data-structure during
// iteration.
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(
&CoordinatorImpl::FinalizeVmRegionDumpIfAllManagersReplied,
weak_ptr_factory_.GetWeakPtr(), pair.second->dump_guid));
}
size_t num_deleted = clients_.erase(client_process);
DCHECK(num_deleted == 1);
}
void CoordinatorImpl::RequestGlobalMemoryDumpInternal(
const QueuedRequest::Args& args,
RequestGlobalMemoryDumpInternalCallback callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
UMA_HISTOGRAM_COUNTS_1000(
"Memory.Experimental.Debug.GlobalDumpQueueLength",
base::saturated_cast<int32_t>(queued_memory_dump_requests_.size()));
bool another_dump_is_queued = !queued_memory_dump_requests_.empty();
// If this is a periodic or peak memory dump request and there already is
// another request in the queue with the same level of detail, there's no
// point in enqueuing this request.
if (another_dump_is_queued &&
args.dump_type == MemoryDumpType::PERIODIC_INTERVAL) {
for (const auto& request : queued_memory_dump_requests_) {
if (request.args.level_of_detail == args.level_of_detail) {
VLOG(1) << "RequestGlobalMemoryDump("
<< base::trace_event::MemoryDumpTypeToString(args.dump_type)
<< ") skipped because another dump request with the same "
"level of detail ("
<< base::trace_event::MemoryDumpLevelOfDetailToString(
args.level_of_detail)
<< ") is already in the queue";
std::move(callback).Run(false /* success */, 0 /* dump_guid */,
nullptr /* global_memory_dump */);
return;
}
}
}
queued_memory_dump_requests_.emplace_back(args, ++next_dump_id_,
std::move(callback));
// If another dump is already in queued, this dump will automatically be
// scheduled when the other dump finishes.
if (another_dump_is_queued)
return;
PerformNextQueuedGlobalMemoryDump();
}
void CoordinatorImpl::OnQueuedRequestTimedOut(uint64_t dump_guid) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
QueuedRequest* request = GetCurrentRequest();
// TODO(lalitm): add metrics for how often this happens.
// Only consider the current request timed out if we fired off this
// delayed callback in association with this request.
if (!request || request->dump_guid != dump_guid)
return;
// Fail all remaining dumps being waited upon and clear the vector.
request->failed_memory_dump_count += request->pending_responses.size();
request->pending_responses.clear();
// Callback the consumer of the service.
FinalizeGlobalMemoryDumpIfAllManagersReplied();
}
void CoordinatorImpl::OnHeapDumpTimeOut(uint64_t dump_guid) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
QueuedRequest* request = GetCurrentRequest();
// TODO(lalitm): add metrics for how often this happens.
// Only consider the current request timed out if we fired off this
// delayed callback in association with this request.
if (!request || request->dump_guid != dump_guid)
return;
// Fail all remaining dumps being waited upon and clear the vector.
if (request->heap_dump_in_progress) {
request->heap_dump_in_progress = false;
FinalizeGlobalMemoryDumpIfAllManagersReplied();
}
}
void CoordinatorImpl::PerformNextQueuedGlobalMemoryDump() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
QueuedRequest* request = GetCurrentRequest();
if (request == nullptr)
return;
auto names_for_pid = ComputePidToServiceNamesMap();
std::vector<QueuedRequestDispatcher::ClientInfo> clients;
for (const auto& kv : clients_) {
auto client_identity = kv.second->identity;
const base::ProcessId pid = GetProcessIdForClientIdentity(client_identity);
if (pid == base::kNullProcessId) {
VLOG(1) << "Couldn't find a PID for client "
<< client_identity.ToString();
continue;
}
clients.emplace_back(kv.second->client.get(), pid, kv.second->process_type,
std::move(names_for_pid[pid]));
}
auto chrome_callback =
base::Bind(&CoordinatorImpl::OnChromeMemoryDumpResponse,
weak_ptr_factory_.GetWeakPtr());
auto os_callback =
base::Bind(&CoordinatorImpl::OnOSMemoryDumpResponse,
weak_ptr_factory_.GetWeakPtr(), request->dump_guid);
QueuedRequestDispatcher::SetUpAndDispatch(request, clients, chrome_callback,
os_callback);
base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&CoordinatorImpl::OnQueuedRequestTimedOut,
weak_ptr_factory_.GetWeakPtr(), request->dump_guid),
client_process_timeout_);
if (request->args.add_to_trace && heap_profiler_) {
request->heap_dump_in_progress = true;
// |IsArgumentFilterEnabled| is the round-about way of asking to anonymize
// the trace. The only way that PII gets leaked is if the full path is
// emitted for mapped files. Passing |strip_path_from_mapped_files|
// is all that is necessary to anonymize the trace.
bool strip_path_from_mapped_files =
base::trace_event::TraceLog::GetInstance()
->GetCurrentTraceConfig()
.IsArgumentFilterEnabled();
heap_profiler_->DumpProcessesForTracing(
strip_path_from_mapped_files,
base::BindOnce(&CoordinatorImpl::OnDumpProcessesForTracing,
weak_ptr_factory_.GetWeakPtr(), request->dump_guid));
base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&CoordinatorImpl::OnHeapDumpTimeOut,
weak_ptr_factory_.GetWeakPtr(), request->dump_guid),
kHeapDumpTimeout);
}
// Run the callback in case there are no client processes registered.
FinalizeGlobalMemoryDumpIfAllManagersReplied();
}
QueuedRequest* CoordinatorImpl::GetCurrentRequest() {
if (queued_memory_dump_requests_.empty()) {
return nullptr;
}
return &queued_memory_dump_requests_.front();
}
void CoordinatorImpl::OnChromeMemoryDumpResponse(
mojom::ClientProcess* client,
bool success,
uint64_t dump_guid,
std::unique_ptr<base::trace_event::ProcessMemoryDump> chrome_memory_dump) {
using ResponseType = QueuedRequest::PendingResponse::Type;
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
QueuedRequest* request = GetCurrentRequest();
if (request == nullptr || request->dump_guid != dump_guid) {
return;
}
RemovePendingResponse(client, ResponseType::kChromeDump);
if (!clients_.count(client)) {
VLOG(1) << "Received a memory dump response from an unregistered client";
return;
}
auto* response = &request->responses[client];
response->chrome_dump = std::move(chrome_memory_dump);
if (!success) {
request->failed_memory_dump_count++;
VLOG(1) << "RequestGlobalMemoryDump() FAIL: NACK from client process";
}
FinalizeGlobalMemoryDumpIfAllManagersReplied();
}
void CoordinatorImpl::OnOSMemoryDumpResponse(uint64_t dump_guid,
mojom::ClientProcess* client,
bool success,
OSMemDumpMap os_dumps) {
using ResponseType = QueuedRequest::PendingResponse::Type;
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
QueuedRequest* request = GetCurrentRequest();
if (request == nullptr || request->dump_guid != dump_guid) {
return;
}
RemovePendingResponse(client, ResponseType::kOSDump);
if (!clients_.count(client)) {
VLOG(1) << "Received a memory dump response from an unregistered client";
return;
}
request->responses[client].os_dumps = std::move(os_dumps);
if (!success) {
request->failed_memory_dump_count++;
VLOG(1) << "RequestGlobalMemoryDump() FAIL: NACK from client process";
}
FinalizeGlobalMemoryDumpIfAllManagersReplied();
}
void CoordinatorImpl::OnOSMemoryDumpForVMRegions(uint64_t dump_guid,
mojom::ClientProcess* client,
bool success,
OSMemDumpMap os_dumps) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
auto request_it = in_progress_vm_region_requests_.find(dump_guid);
DCHECK(request_it != in_progress_vm_region_requests_.end());
QueuedVmRegionRequest* request = request_it->second.get();
auto it = request->pending_responses.find(client);
DCHECK(it != request->pending_responses.end());
request->pending_responses.erase(it);
request->responses[client].os_dumps = std::move(os_dumps);
FinalizeVmRegionDumpIfAllManagersReplied(request->dump_guid);
}
void CoordinatorImpl::FinalizeVmRegionDumpIfAllManagersReplied(
uint64_t dump_guid) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
auto it = in_progress_vm_region_requests_.find(dump_guid);
if (it == in_progress_vm_region_requests_.end())
return;
if (!it->second->pending_responses.empty())
return;
QueuedRequestDispatcher::VmRegions results =
QueuedRequestDispatcher::FinalizeVmRegionRequest(it->second.get());
std::move(it->second->callback).Run(std::move(results));
in_progress_vm_region_requests_.erase(it);
}
void CoordinatorImpl::OnDumpProcessesForTracing(
uint64_t dump_guid,
std::vector<mojom::HeapProfileResultPtr> heap_profile_results) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
QueuedRequest* request = GetCurrentRequest();
if (!request || request->dump_guid != dump_guid) {
return;
}
request->heap_dump_in_progress = false;
for (auto& result : heap_profile_results) {
base::trace_event::TraceArguments args(
"dumps", std::make_unique<StringWrapper>(std::move(result->json)));
// Using the same id merges all of the heap dumps into a single detailed
// dump node in the UI.
TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_PROCESS_ID(
TRACE_EVENT_PHASE_MEMORY_DUMP,
base::trace_event::TraceLog::GetCategoryGroupEnabled(
base::trace_event::MemoryDumpManager::kTraceCategory),
"periodic_interval", trace_event_internal::kGlobalScope, dump_guid,
result->pid, &args, TRACE_EVENT_FLAG_HAS_ID);
}
FinalizeGlobalMemoryDumpIfAllManagersReplied();
}
void CoordinatorImpl::RemovePendingResponse(
mojom::ClientProcess* client,
QueuedRequest::PendingResponse::Type type) {
QueuedRequest* request = GetCurrentRequest();
if (request == nullptr) {
NOTREACHED() << "No current dump request.";
return;
}
auto it = request->pending_responses.find({client, type});
if (it == request->pending_responses.end()) {
VLOG(1) << "Unexpected memory dump response";
return;
}
request->pending_responses.erase(it);
}
void CoordinatorImpl::FinalizeGlobalMemoryDumpIfAllManagersReplied() {
TRACE_EVENT0(base::trace_event::MemoryDumpManager::kTraceCategory,
"GlobalMemoryDump.Computation");
DCHECK(!queued_memory_dump_requests_.empty());
QueuedRequest* request = &queued_memory_dump_requests_.front();
if (!request->dump_in_progress || request->pending_responses.size() > 0 ||
request->heap_dump_in_progress) {
return;
}
QueuedRequestDispatcher::Finalize(request, tracing_observer_.get());
queued_memory_dump_requests_.pop_front();
request = nullptr;
// Schedule the next queued dump (if applicable).
if (!queued_memory_dump_requests_.empty()) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&CoordinatorImpl::PerformNextQueuedGlobalMemoryDump,
weak_ptr_factory_.GetWeakPtr()));
}
}
CoordinatorImpl::ClientInfo::ClientInfo(
const service_manager::Identity& identity,
mojom::ClientProcessPtr client,
mojom::ProcessType process_type)
: identity(identity),
client(std::move(client)),
process_type(process_type) {}
CoordinatorImpl::ClientInfo::~ClientInfo() {}
} // namespace memory_instrumentation