| // 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/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/interfaces/memory_instrumentation/constants.mojom.h" |
| #include "services/resource_coordinator/public/interfaces/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; |
| |
| } // 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)) { |
| 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); |
| } |
| |
| 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, |
| const RequestGlobalMemoryDumpCallback& callback) { |
| // Don't allow arbitary processes to obtain VM regions. Only the heap profiler |
| // is allowed to obtain them using the special method on the different |
| // interface. |
| if (level_of_detail == |
| MemoryDumpLevelOfDetail::VM_REGIONS_ONLY_FOR_HEAP_PROFILER) { |
| bindings_.ReportBadMessage( |
| "Requested global memory dump using level of detail reserved for the " |
| "heap profiler."); |
| return; |
| } |
| |
| // This merely strips out the |dump_guid| argument. |
| auto adapter = [](const RequestGlobalMemoryDumpCallback& callback, |
| bool success, uint64_t, |
| mojom::GlobalMemoryDumpPtr global_memory_dump) { |
| 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); |
| RequestGlobalMemoryDumpInternal(args, base::BindRepeating(adapter, callback)); |
| } |
| |
| void CoordinatorImpl::RequestGlobalMemoryDumpForPid( |
| base::ProcessId pid, |
| const 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) { |
| 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 = [](const RequestGlobalMemoryDumpForPidCallback& callback, |
| bool success, uint64_t, |
| mojom::GlobalMemoryDumpPtr global_memory_dump) { |
| 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); |
| RequestGlobalMemoryDumpInternal(args, base::BindRepeating(adapter, callback)); |
| } |
| |
| void CoordinatorImpl::RequestGlobalMemoryDumpAndAppendToTrace( |
| MemoryDumpType dump_type, |
| MemoryDumpLevelOfDetail level_of_detail, |
| const RequestGlobalMemoryDumpAndAppendToTraceCallback& callback) { |
| // Don't allow arbitary processes to obtain VM regions. Only the heap profiler |
| // is allowed to obtain them using the special method on its own dedicated |
| // interface (HeapProfilingHelper). |
| if (level_of_detail == |
| MemoryDumpLevelOfDetail::VM_REGIONS_ONLY_FOR_HEAP_PROFILER) { |
| bindings_.ReportBadMessage( |
| "Requested global memory dump using level of detail reserved for the " |
| "heap profiler."); |
| return; |
| } |
| |
| // This merely strips out the |dump_ptr| argument. |
| auto adapter = |
| [](const RequestGlobalMemoryDumpAndAppendToTraceCallback& callback, |
| bool success, uint64_t dump_guid, |
| mojom::GlobalMemoryDumpPtr) { callback.Run(success, dump_guid); }; |
| |
| QueuedRequest::Args args(dump_type, level_of_detail, {}, |
| true /* add_to_trace */, base::kNullProcessId); |
| RequestGlobalMemoryDumpInternal(args, base::BindRepeating(adapter, callback)); |
| } |
| |
| void CoordinatorImpl::GetVmRegionsForHeapProfiler( |
| const GetVmRegionsForHeapProfilerCallback& callback) { |
| // This merely strips out the |dump_guid| argument. |
| auto adapter = [](const RequestGlobalMemoryDumpCallback& callback, |
| bool success, uint64_t dump_guid, |
| mojom::GlobalMemoryDumpPtr global_memory_dump) { |
| callback.Run(success, std::move(global_memory_dump)); |
| }; |
| |
| QueuedRequest::Args args( |
| MemoryDumpType::EXPLICITLY_TRIGGERED, |
| MemoryDumpLevelOfDetail::VM_REGIONS_ONLY_FOR_HEAP_PROFILER, {}, |
| false /* add_to_trace */, base::kNullProcessId); |
| RequestGlobalMemoryDumpInternal(args, base::BindRepeating(adapter, callback)); |
| } |
| |
| 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::Bind(&CoordinatorImpl::UnregisterClientProcess, |
| base::Unretained(this), 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(); |
| } |
| size_t num_deleted = clients_.erase(client_process); |
| DCHECK(num_deleted == 1); |
| } |
| |
| void CoordinatorImpl::RequestGlobalMemoryDumpInternal( |
| const QueuedRequest::Args& args, |
| const RequestGlobalMemoryDumpInternalCallback& callback) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| UMA_HISTOGRAM_COUNTS_1000("Memory.Experimental.Debug.GlobalDumpQueueLength", |
| 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 || |
| args.dump_type == MemoryDumpType::PEAK_MEMORY_USAGE)) { |
| 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"; |
| callback.Run(false /* success */, 0 /* dump_guid */, |
| nullptr /* global_memory_dump */); |
| return; |
| } |
| } |
| } |
| |
| queued_memory_dump_requests_.emplace_back(args, ++next_dump_id_, 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::PerformNextQueuedGlobalMemoryDump() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| QueuedRequest* request = GetCurrentRequest(); |
| |
| 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.name() |
| << "." << client_identity.instance() << "\""; |
| continue; |
| } |
| clients.emplace_back(kv.second->client.get(), pid, kv.second->process_type); |
| } |
| |
| auto chrome_callback = base::Bind( |
| &CoordinatorImpl::OnChromeMemoryDumpResponse, base::Unretained(this)); |
| auto os_callback = base::Bind(&CoordinatorImpl::OnOSMemoryDumpResponse, |
| base::Unretained(this), request->dump_guid); |
| QueuedRequestDispatcher::SetUpAndDispatch(request, clients, chrome_callback, |
| os_callback); |
| |
| base::SequencedTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&CoordinatorImpl::OnQueuedRequestTimedOut, |
| base::Unretained(this), request->dump_guid), |
| client_process_timeout_); |
| |
| // 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::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) |
| 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::Bind(&CoordinatorImpl::PerformNextQueuedGlobalMemoryDump, |
| base::Unretained(this))); |
| } |
| } |
| |
| 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 |