diff options
Diffstat (limited to 'base/trace_event/memory_dump_manager.cc')
-rw-r--r-- | base/trace_event/memory_dump_manager.cc | 545 |
1 files changed, 545 insertions, 0 deletions
diff --git a/base/trace_event/memory_dump_manager.cc b/base/trace_event/memory_dump_manager.cc new file mode 100644 index 0000000000..d61528af8e --- /dev/null +++ b/base/trace_event/memory_dump_manager.cc @@ -0,0 +1,545 @@ +// Copyright 2015 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 "base/trace_event/memory_dump_manager.h" + +#include <inttypes.h> +#include <stdio.h> + +#include <algorithm> +#include <memory> +#include <utility> + +#include "base/allocator/buildflags.h" +#include "base/base_switches.h" +#include "base/command_line.h" +#include "base/debug/alias.h" +#include "base/debug/stack_trace.h" +#include "base/debug/thread_heap_usage_tracker.h" +#include "base/memory/ptr_util.h" +#include "base/sequenced_task_runner.h" +#include "base/strings/string_util.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" +#include "base/threading/thread.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/trace_event/heap_profiler.h" +#include "base/trace_event/heap_profiler_allocation_context_tracker.h" +#include "base/trace_event/heap_profiler_event_filter.h" +#include "base/trace_event/malloc_dump_provider.h" +#include "base/trace_event/memory_dump_provider.h" +#include "base/trace_event/memory_dump_scheduler.h" +#include "base/trace_event/memory_infra_background_whitelist.h" +#include "base/trace_event/process_memory_dump.h" +#include "base/trace_event/trace_event.h" +#include "base/trace_event/trace_event_argument.h" +#include "build/build_config.h" + +#if defined(OS_ANDROID) +#include "base/trace_event/java_heap_dump_provider_android.h" + +#if BUILDFLAG(CAN_UNWIND_WITH_CFI_TABLE) +#include "base/trace_event/cfi_backtrace_android.h" +#endif + +#endif // defined(OS_ANDROID) + +namespace base { +namespace trace_event { + +namespace { + +MemoryDumpManager* g_memory_dump_manager_for_testing = nullptr; + +// Temporary (until scheduler is moved outside of here) +// trampoline function to match the |request_dump_function| passed to Initialize +// to the callback expected by MemoryDumpScheduler. +// TODO(primiano): remove this. +void DoGlobalDumpWithoutCallback( + MemoryDumpManager::RequestGlobalDumpFunction global_dump_fn, + MemoryDumpType dump_type, + MemoryDumpLevelOfDetail level_of_detail) { + global_dump_fn.Run(dump_type, level_of_detail); +} + +} // namespace + +// static +const char* const MemoryDumpManager::kTraceCategory = + TRACE_DISABLED_BY_DEFAULT("memory-infra"); + +// static +const int MemoryDumpManager::kMaxConsecutiveFailuresCount = 3; + +// static +const uint64_t MemoryDumpManager::kInvalidTracingProcessId = 0; + +// static +const char* const MemoryDumpManager::kSystemAllocatorPoolName = +#if defined(MALLOC_MEMORY_TRACING_SUPPORTED) + MallocDumpProvider::kAllocatedObjects; +#else + nullptr; +#endif + +// static +MemoryDumpManager* MemoryDumpManager::GetInstance() { + if (g_memory_dump_manager_for_testing) + return g_memory_dump_manager_for_testing; + + return Singleton<MemoryDumpManager, + LeakySingletonTraits<MemoryDumpManager>>::get(); +} + +// static +std::unique_ptr<MemoryDumpManager> +MemoryDumpManager::CreateInstanceForTesting() { + DCHECK(!g_memory_dump_manager_for_testing); + std::unique_ptr<MemoryDumpManager> instance(new MemoryDumpManager()); + g_memory_dump_manager_for_testing = instance.get(); + return instance; +} + +MemoryDumpManager::MemoryDumpManager() + : is_coordinator_(false), + tracing_process_id_(kInvalidTracingProcessId), + dumper_registrations_ignored_for_testing_(false) {} + +MemoryDumpManager::~MemoryDumpManager() { + Thread* dump_thread = nullptr; + { + AutoLock lock(lock_); + if (dump_thread_) { + dump_thread = dump_thread_.get(); + } + } + if (dump_thread) { + dump_thread->Stop(); + } + AutoLock lock(lock_); + dump_thread_.reset(); + g_memory_dump_manager_for_testing = nullptr; +} + +void MemoryDumpManager::Initialize( + RequestGlobalDumpFunction request_dump_function, + bool is_coordinator) { + { + AutoLock lock(lock_); + DCHECK(!request_dump_function.is_null()); + DCHECK(!can_request_global_dumps()); + request_dump_function_ = request_dump_function; + is_coordinator_ = is_coordinator; + } + +// Enable the core dump providers. +#if defined(MALLOC_MEMORY_TRACING_SUPPORTED) + RegisterDumpProvider(MallocDumpProvider::GetInstance(), "Malloc", nullptr); +#endif + +#if defined(OS_ANDROID) + RegisterDumpProvider(JavaHeapDumpProvider::GetInstance(), "JavaHeap", + nullptr); +#endif + + TRACE_EVENT_WARMUP_CATEGORY(kTraceCategory); +} + +void MemoryDumpManager::RegisterDumpProvider( + MemoryDumpProvider* mdp, + const char* name, + scoped_refptr<SingleThreadTaskRunner> task_runner, + MemoryDumpProvider::Options options) { + options.dumps_on_single_thread_task_runner = true; + RegisterDumpProviderInternal(mdp, name, std::move(task_runner), options); +} + +void MemoryDumpManager::RegisterDumpProvider( + MemoryDumpProvider* mdp, + const char* name, + scoped_refptr<SingleThreadTaskRunner> task_runner) { + // Set |dumps_on_single_thread_task_runner| to true because all providers + // without task runner are run on dump thread. + MemoryDumpProvider::Options options; + options.dumps_on_single_thread_task_runner = true; + RegisterDumpProviderInternal(mdp, name, std::move(task_runner), options); +} + +void MemoryDumpManager::RegisterDumpProviderWithSequencedTaskRunner( + MemoryDumpProvider* mdp, + const char* name, + scoped_refptr<SequencedTaskRunner> task_runner, + MemoryDumpProvider::Options options) { + DCHECK(task_runner); + options.dumps_on_single_thread_task_runner = false; + RegisterDumpProviderInternal(mdp, name, std::move(task_runner), options); +} + +void MemoryDumpManager::RegisterDumpProviderInternal( + MemoryDumpProvider* mdp, + const char* name, + scoped_refptr<SequencedTaskRunner> task_runner, + const MemoryDumpProvider::Options& options) { + if (dumper_registrations_ignored_for_testing_) + return; + + // Only a handful of MDPs are required to compute the memory metrics. These + // have small enough performance overhead that it is resonable to run them + // in the background while the user is doing other things. Those MDPs are + // 'whitelisted for background mode'. + bool whitelisted_for_background_mode = IsMemoryDumpProviderWhitelisted(name); + + scoped_refptr<MemoryDumpProviderInfo> mdpinfo = + new MemoryDumpProviderInfo(mdp, name, std::move(task_runner), options, + whitelisted_for_background_mode); + + { + AutoLock lock(lock_); + bool already_registered = !dump_providers_.insert(mdpinfo).second; + // This actually happens in some tests which don't have a clean tear-down + // path for RenderThreadImpl::Init(). + if (already_registered) + return; + } +} + +void MemoryDumpManager::UnregisterDumpProvider(MemoryDumpProvider* mdp) { + UnregisterDumpProviderInternal(mdp, false /* delete_async */); +} + +void MemoryDumpManager::UnregisterAndDeleteDumpProviderSoon( + std::unique_ptr<MemoryDumpProvider> mdp) { + UnregisterDumpProviderInternal(mdp.release(), true /* delete_async */); +} + +void MemoryDumpManager::UnregisterDumpProviderInternal( + MemoryDumpProvider* mdp, + bool take_mdp_ownership_and_delete_async) { + std::unique_ptr<MemoryDumpProvider> owned_mdp; + if (take_mdp_ownership_and_delete_async) + owned_mdp.reset(mdp); + + AutoLock lock(lock_); + + auto mdp_iter = dump_providers_.begin(); + for (; mdp_iter != dump_providers_.end(); ++mdp_iter) { + if ((*mdp_iter)->dump_provider == mdp) + break; + } + + if (mdp_iter == dump_providers_.end()) + return; // Not registered / already unregistered. + + if (take_mdp_ownership_and_delete_async) { + // The MDP will be deleted whenever the MDPInfo struct will, that is either: + // - At the end of this function, if no dump is in progress. + // - In ContinueAsyncProcessDump() when MDPInfo is removed from + // |pending_dump_providers|. + DCHECK(!(*mdp_iter)->owned_dump_provider); + (*mdp_iter)->owned_dump_provider = std::move(owned_mdp); + } else { + // If you hit this DCHECK, your dump provider has a bug. + // Unregistration of a MemoryDumpProvider is safe only if: + // - The MDP has specified a sequenced task runner affinity AND the + // unregistration happens on the same task runner. So that the MDP cannot + // unregister and be in the middle of a OnMemoryDump() at the same time. + // - The MDP has NOT specified a task runner affinity and its ownership is + // transferred via UnregisterAndDeleteDumpProviderSoon(). + // In all the other cases, it is not possible to guarantee that the + // unregistration will not race with OnMemoryDump() calls. + DCHECK((*mdp_iter)->task_runner && + (*mdp_iter)->task_runner->RunsTasksInCurrentSequence()) + << "MemoryDumpProvider \"" << (*mdp_iter)->name << "\" attempted to " + << "unregister itself in a racy way. Please file a crbug."; + } + + // The MDPInfo instance can still be referenced by the + // |ProcessMemoryDumpAsyncState.pending_dump_providers|. For this reason + // the MDPInfo is flagged as disabled. It will cause InvokeOnMemoryDump() + // to just skip it, without actually invoking the |mdp|, which might be + // destroyed by the caller soon after this method returns. + (*mdp_iter)->disabled = true; + dump_providers_.erase(mdp_iter); +} + +bool MemoryDumpManager::IsDumpProviderRegisteredForTesting( + MemoryDumpProvider* provider) { + AutoLock lock(lock_); + + for (const auto& info : dump_providers_) { + if (info->dump_provider == provider) + return true; + } + return false; +} + +scoped_refptr<base::SequencedTaskRunner> +MemoryDumpManager::GetOrCreateBgTaskRunnerLocked() { + lock_.AssertAcquired(); + + if (dump_thread_) + return dump_thread_->task_runner(); + + dump_thread_ = std::make_unique<Thread>("MemoryInfra"); + bool started = dump_thread_->Start(); + CHECK(started); + + return dump_thread_->task_runner(); +} + +void MemoryDumpManager::CreateProcessDump( + const MemoryDumpRequestArgs& args, + const ProcessMemoryDumpCallback& callback) { + char guid_str[20]; + sprintf(guid_str, "0x%" PRIx64, args.dump_guid); + TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(kTraceCategory, "ProcessMemoryDump", + TRACE_ID_LOCAL(args.dump_guid), "dump_guid", + TRACE_STR_COPY(guid_str)); + + // If argument filter is enabled then only background mode dumps should be + // allowed. In case the trace config passed for background tracing session + // missed the allowed modes argument, it crashes here instead of creating + // unexpected dumps. + if (TraceLog::GetInstance() + ->GetCurrentTraceConfig() + .IsArgumentFilterEnabled()) { + CHECK_EQ(MemoryDumpLevelOfDetail::BACKGROUND, args.level_of_detail); + } + + std::unique_ptr<ProcessMemoryDumpAsyncState> pmd_async_state; + { + AutoLock lock(lock_); + + pmd_async_state.reset(new ProcessMemoryDumpAsyncState( + args, dump_providers_, callback, GetOrCreateBgTaskRunnerLocked())); + } + + // Start the process dump. This involves task runner hops as specified by the + // MemoryDumpProvider(s) in RegisterDumpProvider()). + ContinueAsyncProcessDump(pmd_async_state.release()); +} + +// Invokes OnMemoryDump() on all MDPs that are next in the pending list and run +// on the current sequenced task runner. If the next MDP does not run in current +// sequenced task runner, then switches to that task runner and continues. All +// OnMemoryDump() invocations are linearized. |lock_| is used in these functions +// purely to ensure consistency w.r.t. (un)registrations of |dump_providers_|. +void MemoryDumpManager::ContinueAsyncProcessDump( + ProcessMemoryDumpAsyncState* owned_pmd_async_state) { + HEAP_PROFILER_SCOPED_IGNORE; + // Initalizes the ThreadLocalEventBuffer to guarantee that the TRACE_EVENTs + // in the PostTask below don't end up registering their own dump providers + // (for discounting trace memory overhead) while holding the |lock_|. + TraceLog::GetInstance()->InitializeThreadLocalEventBufferIfSupported(); + + // In theory |owned_pmd_async_state| should be a unique_ptr. The only reason + // why it isn't is because of the corner case logic of |did_post_task| + // above, which needs to take back the ownership of the |pmd_async_state| when + // the PostTask() fails. + // Unfortunately, PostTask() destroys the unique_ptr arguments upon failure + // to prevent accidental leaks. Using a unique_ptr would prevent us to to + // skip the hop and move on. Hence the manual naked -> unique ptr juggling. + auto pmd_async_state = WrapUnique(owned_pmd_async_state); + owned_pmd_async_state = nullptr; + + while (!pmd_async_state->pending_dump_providers.empty()) { + // Read MemoryDumpProviderInfo thread safety considerations in + // memory_dump_manager.h when accessing |mdpinfo| fields. + MemoryDumpProviderInfo* mdpinfo = + pmd_async_state->pending_dump_providers.back().get(); + + // If we are in background mode, we should invoke only the whitelisted + // providers. Ignore other providers and continue. + if (pmd_async_state->req_args.level_of_detail == + MemoryDumpLevelOfDetail::BACKGROUND && + !mdpinfo->whitelisted_for_background_mode) { + pmd_async_state->pending_dump_providers.pop_back(); + continue; + } + + // If the dump provider did not specify a task runner affinity, dump on + // |dump_thread_|. + scoped_refptr<SequencedTaskRunner> task_runner = mdpinfo->task_runner; + if (!task_runner) { + DCHECK(mdpinfo->options.dumps_on_single_thread_task_runner); + task_runner = pmd_async_state->dump_thread_task_runner; + DCHECK(task_runner); + } + + // If |RunsTasksInCurrentSequence()| is true then no PostTask is + // required since we are on the right SequencedTaskRunner. + if (task_runner->RunsTasksInCurrentSequence()) { + InvokeOnMemoryDump(mdpinfo, pmd_async_state->process_memory_dump.get()); + pmd_async_state->pending_dump_providers.pop_back(); + continue; + } + + bool did_post_task = task_runner->PostTask( + FROM_HERE, + BindOnce(&MemoryDumpManager::ContinueAsyncProcessDump, Unretained(this), + Unretained(pmd_async_state.get()))); + + if (did_post_task) { + // Ownership is tranferred to the posted task. + ignore_result(pmd_async_state.release()); + return; + } + + // PostTask usually fails only if the process or thread is shut down. So, + // the dump provider is disabled here. But, don't disable unbound dump + // providers, since the |dump_thread_| is controlled by MDM. + if (mdpinfo->task_runner) { + // A locked access is required to R/W |disabled| (for the + // UnregisterAndDeleteDumpProviderSoon() case). + AutoLock lock(lock_); + mdpinfo->disabled = true; + } + + // PostTask failed. Ignore the dump provider and continue. + pmd_async_state->pending_dump_providers.pop_back(); + } + + FinishAsyncProcessDump(std::move(pmd_async_state)); +} + +// This function is called on the right task runner for current MDP. It is +// either the task runner specified by MDP or |dump_thread_task_runner| if the +// MDP did not specify task runner. Invokes the dump provider's OnMemoryDump() +// (unless disabled). +void MemoryDumpManager::InvokeOnMemoryDump(MemoryDumpProviderInfo* mdpinfo, + ProcessMemoryDump* pmd) { + HEAP_PROFILER_SCOPED_IGNORE; + DCHECK(!mdpinfo->task_runner || + mdpinfo->task_runner->RunsTasksInCurrentSequence()); + + TRACE_EVENT1(kTraceCategory, "MemoryDumpManager::InvokeOnMemoryDump", + "dump_provider.name", mdpinfo->name); + + // Do not add any other TRACE_EVENT macro (or function that might have them) + // below this point. Under some rare circunstances, they can re-initialize + // and invalide the current ThreadLocalEventBuffer MDP, making the + // |should_dump| check below susceptible to TOCTTOU bugs + // (https://crbug.com/763365). + + bool is_thread_bound; + { + // A locked access is required to R/W |disabled| (for the + // UnregisterAndDeleteDumpProviderSoon() case). + AutoLock lock(lock_); + + // Unregister the dump provider if it failed too many times consecutively. + if (!mdpinfo->disabled && + mdpinfo->consecutive_failures >= kMaxConsecutiveFailuresCount) { + mdpinfo->disabled = true; + DLOG(ERROR) << "Disabling MemoryDumpProvider \"" << mdpinfo->name + << "\". Dump failed multiple times consecutively."; + } + if (mdpinfo->disabled) + return; + + is_thread_bound = mdpinfo->task_runner != nullptr; + } // AutoLock lock(lock_); + + // Invoke the dump provider. + + // A stack allocated string with dump provider name is useful to debug + // crashes while invoking dump after a |dump_provider| is not unregistered + // in safe way. + char provider_name_for_debugging[16]; + strncpy(provider_name_for_debugging, mdpinfo->name, + sizeof(provider_name_for_debugging) - 1); + provider_name_for_debugging[sizeof(provider_name_for_debugging) - 1] = '\0'; + base::debug::Alias(provider_name_for_debugging); + + ANNOTATE_BENIGN_RACE(&mdpinfo->disabled, "best-effort race detection"); + CHECK(!is_thread_bound || + !*(static_cast<volatile bool*>(&mdpinfo->disabled))); + bool dump_successful = + mdpinfo->dump_provider->OnMemoryDump(pmd->dump_args(), pmd); + mdpinfo->consecutive_failures = + dump_successful ? 0 : mdpinfo->consecutive_failures + 1; +} + +void MemoryDumpManager::FinishAsyncProcessDump( + std::unique_ptr<ProcessMemoryDumpAsyncState> pmd_async_state) { + HEAP_PROFILER_SCOPED_IGNORE; + DCHECK(pmd_async_state->pending_dump_providers.empty()); + const uint64_t dump_guid = pmd_async_state->req_args.dump_guid; + if (!pmd_async_state->callback_task_runner->BelongsToCurrentThread()) { + scoped_refptr<SingleThreadTaskRunner> callback_task_runner = + pmd_async_state->callback_task_runner; + callback_task_runner->PostTask( + FROM_HERE, BindOnce(&MemoryDumpManager::FinishAsyncProcessDump, + Unretained(this), std::move(pmd_async_state))); + return; + } + + TRACE_EVENT0(kTraceCategory, "MemoryDumpManager::FinishAsyncProcessDump"); + + if (!pmd_async_state->callback.is_null()) { + pmd_async_state->callback.Run( + true /* success */, dump_guid, + std::move(pmd_async_state->process_memory_dump)); + pmd_async_state->callback.Reset(); + } + + TRACE_EVENT_NESTABLE_ASYNC_END0(kTraceCategory, "ProcessMemoryDump", + TRACE_ID_LOCAL(dump_guid)); +} + +void MemoryDumpManager::SetupForTracing( + const TraceConfig::MemoryDumpConfig& memory_dump_config) { + AutoLock lock(lock_); + + // At this point we must have the ability to request global dumps. + DCHECK(can_request_global_dumps()); + + MemoryDumpScheduler::Config periodic_config; + for (const auto& trigger : memory_dump_config.triggers) { + if (trigger.trigger_type == MemoryDumpType::PERIODIC_INTERVAL) { + if (periodic_config.triggers.empty()) { + periodic_config.callback = + BindRepeating(&DoGlobalDumpWithoutCallback, request_dump_function_, + MemoryDumpType::PERIODIC_INTERVAL); + } + periodic_config.triggers.push_back( + {trigger.level_of_detail, trigger.min_time_between_dumps_ms}); + } + } + + // Only coordinator process triggers periodic memory dumps. + if (is_coordinator_ && !periodic_config.triggers.empty()) { + MemoryDumpScheduler::GetInstance()->Start(periodic_config, + GetOrCreateBgTaskRunnerLocked()); + } +} + +void MemoryDumpManager::TeardownForTracing() { + // There might be a memory dump in progress while this happens. Therefore, + // ensure that the MDM state which depends on the tracing enabled / disabled + // state is always accessed by the dumping methods holding the |lock_|. + AutoLock lock(lock_); + + MemoryDumpScheduler::GetInstance()->Stop(); +} + +MemoryDumpManager::ProcessMemoryDumpAsyncState::ProcessMemoryDumpAsyncState( + MemoryDumpRequestArgs req_args, + const MemoryDumpProviderInfo::OrderedSet& dump_providers, + ProcessMemoryDumpCallback callback, + scoped_refptr<SequencedTaskRunner> dump_thread_task_runner) + : req_args(req_args), + callback(callback), + callback_task_runner(ThreadTaskRunnerHandle::Get()), + dump_thread_task_runner(std::move(dump_thread_task_runner)) { + pending_dump_providers.reserve(dump_providers.size()); + pending_dump_providers.assign(dump_providers.rbegin(), dump_providers.rend()); + MemoryDumpArgs args = {req_args.level_of_detail, req_args.dump_guid}; + process_memory_dump = std::make_unique<ProcessMemoryDump>(args); +} + +MemoryDumpManager::ProcessMemoryDumpAsyncState::~ProcessMemoryDumpAsyncState() = + default; + +} // namespace trace_event +} // namespace base |