// 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 #include #include "base/atomic_sequence_num.h" #include "base/base_switches.h" #include "base/command_line.h" #include "base/compiler_specific.h" #include "base/thread_task_runner_handle.h" #include "base/threading/thread.h" #include "base/trace_event/heap_profiler_allocation_context_tracker.h" #include "base/trace_event/heap_profiler_stack_frame_deduplicator.h" #include "base/trace_event/heap_profiler_type_name_deduplicator.h" #include "base/trace_event/malloc_dump_provider.h" #include "base/trace_event/memory_dump_provider.h" #include "base/trace_event/memory_dump_session_state.h" #include "base/trace_event/process_memory_dump.h" #include "base/trace_event/trace_event_argument.h" #include "build/build_config.h" #if !defined(OS_NACL) #include "base/trace_event/process_memory_totals_dump_provider.h" #endif #if defined(OS_LINUX) || defined(OS_ANDROID) #include "base/trace_event/process_memory_maps_dump_provider.h" #endif #if defined(OS_ANDROID) #include "base/trace_event/java_heap_dump_provider_android.h" #endif #if defined(OS_WIN) #include "base/trace_event/winheap_dump_provider_win.h" #endif namespace base { namespace trace_event { namespace { const int kTraceEventNumArgs = 1; const char* kTraceEventArgNames[] = {"dumps"}; const unsigned char kTraceEventArgTypes[] = {TRACE_VALUE_TYPE_CONVERTABLE}; StaticAtomicSequenceNumber g_next_guid; uint32_t g_periodic_dumps_count = 0; uint32_t g_heavy_dumps_rate = 0; MemoryDumpManager* g_instance_for_testing = nullptr; void RequestPeriodicGlobalDump() { MemoryDumpLevelOfDetail level_of_detail; if (g_heavy_dumps_rate == 0) { level_of_detail = MemoryDumpLevelOfDetail::LIGHT; } else { level_of_detail = g_periodic_dumps_count == 0 ? MemoryDumpLevelOfDetail::DETAILED : MemoryDumpLevelOfDetail::LIGHT; if (++g_periodic_dumps_count == g_heavy_dumps_rate) g_periodic_dumps_count = 0; } MemoryDumpManager::GetInstance()->RequestGlobalDump( MemoryDumpType::PERIODIC_INTERVAL, level_of_detail); } // Callback wrapper to hook upon the completion of RequestGlobalDump() and // inject trace markers. void OnGlobalDumpDone(MemoryDumpCallback wrapped_callback, uint64_t dump_guid, bool success) { TRACE_EVENT_NESTABLE_ASYNC_END1( MemoryDumpManager::kTraceCategory, "GlobalMemoryDump", TRACE_ID_MANGLE(dump_guid), "success", success); if (!wrapped_callback.is_null()) { wrapped_callback.Run(dump_guid, success); wrapped_callback.Reset(); } } } // 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; #elif defined(OS_WIN) WinHeapDumpProvider::kAllocatedObjects; #else nullptr; #endif // static MemoryDumpManager* MemoryDumpManager::GetInstance() { if (g_instance_for_testing) return g_instance_for_testing; return Singleton>::get(); } // static void MemoryDumpManager::SetInstanceForTesting(MemoryDumpManager* instance) { g_instance_for_testing = instance; } MemoryDumpManager::MemoryDumpManager() : delegate_(nullptr), is_coordinator_(false), memory_tracing_enabled_(0), tracing_process_id_(kInvalidTracingProcessId), dumper_registrations_ignored_for_testing_(false) { g_next_guid.GetNext(); // Make sure that first guid is not zero. heap_profiling_enabled_ = CommandLine::InitializedForCurrentProcess() ? CommandLine::ForCurrentProcess()->HasSwitch( switches::kEnableHeapProfiling) : false; if (heap_profiling_enabled_) AllocationContextTracker::SetCaptureEnabled(true); } MemoryDumpManager::~MemoryDumpManager() { TraceLog::GetInstance()->RemoveEnabledStateObserver(this); } void MemoryDumpManager::Initialize(MemoryDumpManagerDelegate* delegate, bool is_coordinator) { { AutoLock lock(lock_); DCHECK(delegate); DCHECK(!delegate_); delegate_ = delegate; is_coordinator_ = is_coordinator; } // Enable the core dump providers. #if !defined(OS_NACL) RegisterDumpProvider(ProcessMemoryTotalsDumpProvider::GetInstance(), "ProcessMemoryTotals", nullptr); #endif #if defined(MALLOC_MEMORY_TRACING_SUPPORTED) RegisterDumpProvider(MallocDumpProvider::GetInstance(), "Malloc", nullptr); #endif #if defined(OS_LINUX) || defined(OS_ANDROID) RegisterDumpProvider(ProcessMemoryMapsDumpProvider::GetInstance(), "ProcessMemoryMaps", nullptr); #endif #if defined(OS_ANDROID) RegisterDumpProvider(JavaHeapDumpProvider::GetInstance(), "JavaHeap", nullptr); #endif #if defined(OS_WIN) RegisterDumpProvider(WinHeapDumpProvider::GetInstance(), "WinHeap", nullptr); #endif // If tracing was enabled before initializing MemoryDumpManager, we missed the // OnTraceLogEnabled() event. Synthetize it so we can late-join the party. bool is_tracing_already_enabled = TraceLog::GetInstance()->IsEnabled(); TRACE_EVENT0(kTraceCategory, "init"); // Add to trace-viewer category list. TraceLog::GetInstance()->AddEnabledStateObserver(this); if (is_tracing_already_enabled) OnTraceLogEnabled(); } void MemoryDumpManager::RegisterDumpProvider( MemoryDumpProvider* mdp, const char* name, const scoped_refptr& task_runner, const MemoryDumpProvider::Options& options) { if (dumper_registrations_ignored_for_testing_) return; MemoryDumpProviderInfo mdp_info(mdp, name, task_runner, options); AutoLock lock(lock_); auto iter_new = dump_providers_.insert(mdp_info); // If there was a previous entry, replace it with the new one. This is to deal // with the case where a dump provider unregisters itself and then re- // registers before a memory dump happens, so its entry was still in the // collection but flagged |unregistered|. if (!iter_new.second) { dump_providers_.erase(iter_new.first); dump_providers_.insert(mdp_info); } if (heap_profiling_enabled_) mdp->OnHeapProfilingEnabled(true); } void MemoryDumpManager::RegisterDumpProvider( MemoryDumpProvider* mdp, const char* name, const scoped_refptr& task_runner) { RegisterDumpProvider(mdp, name, task_runner, MemoryDumpProvider::Options()); } void MemoryDumpManager::UnregisterDumpProvider(MemoryDumpProvider* 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; // Unregistration of a MemoryDumpProvider while tracing is ongoing is safe // only if the MDP has specified a thread affinity (via task_runner()) AND // the unregistration happens on the same thread (so the MDP cannot unregister // and OnMemoryDump() at the same time). // Otherwise, it is not possible to guarantee that its unregistration is // race-free. If you hit this DCHECK, your MDP has a bug. DCHECK(!subtle::NoBarrier_Load(&memory_tracing_enabled_) || (mdp_iter->task_runner && mdp_iter->task_runner->BelongsToCurrentThread())) << "MemoryDumpProvider \"" << mdp_iter->name << "\" attempted to " << "unregister itself in a racy way. Please file a crbug."; mdp_iter->unregistered = true; } void MemoryDumpManager::RequestGlobalDump( MemoryDumpType dump_type, MemoryDumpLevelOfDetail level_of_detail, const MemoryDumpCallback& callback) { // Bail out immediately if tracing is not enabled at all. if (!UNLIKELY(subtle::NoBarrier_Load(&memory_tracing_enabled_))) { if (!callback.is_null()) callback.Run(0u /* guid */, false /* success */); return; } const uint64_t guid = TraceLog::GetInstance()->MangleEventId(g_next_guid.GetNext()); // Creates an async event to keep track of the global dump evolution. // The |wrapped_callback| will generate the ASYNC_END event and then invoke // the real |callback| provided by the caller. TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(kTraceCategory, "GlobalMemoryDump", TRACE_ID_MANGLE(guid)); MemoryDumpCallback wrapped_callback = Bind(&OnGlobalDumpDone, callback); // Technically there is no need to grab the |lock_| here as the delegate is // long-lived and can only be set by Initialize(), which is locked and // necessarily happens before memory_tracing_enabled_ == true. // Not taking the |lock_|, though, is lakely make TSan barf and, at this point // (memory-infra is enabled) we're not in the fast-path anymore. MemoryDumpManagerDelegate* delegate; { AutoLock lock(lock_); delegate = delegate_; } // The delegate will coordinate the IPC broadcast and at some point invoke // CreateProcessDump() to get a dump for the current process. MemoryDumpRequestArgs args = {guid, dump_type, level_of_detail}; delegate->RequestGlobalMemoryDump(args, wrapped_callback); } void MemoryDumpManager::RequestGlobalDump( MemoryDumpType dump_type, MemoryDumpLevelOfDetail level_of_detail) { RequestGlobalDump(dump_type, level_of_detail, MemoryDumpCallback()); } void MemoryDumpManager::CreateProcessDump(const MemoryDumpRequestArgs& args, const MemoryDumpCallback& callback) { TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(kTraceCategory, "ProcessMemoryDump", TRACE_ID_MANGLE(args.dump_guid)); scoped_ptr pmd_async_state; { AutoLock lock(lock_); pmd_async_state.reset(new ProcessMemoryDumpAsyncState( args, dump_providers_.begin(), session_state_, callback, dump_thread_->task_runner())); } TRACE_EVENT_WITH_FLOW0(kTraceCategory, "MemoryDumpManager::CreateProcessDump", TRACE_ID_MANGLE(args.dump_guid), TRACE_EVENT_FLAG_FLOW_OUT); // Start the thread hop. |dump_providers_| are kept sorted by thread, so // ContinueAsyncProcessDump will hop at most once per thread (w.r.t. thread // affinity specified by the MemoryDumpProvider(s) in RegisterDumpProvider()). ContinueAsyncProcessDump(std::move(pmd_async_state)); } // At most one ContinueAsyncProcessDump() can be active at any time for a given // PMD, regardless of status of the |lock_|. |lock_| is used here purely to // ensure consistency w.r.t. (un)registrations of |dump_providers_|. // The linearization of dump providers' OnMemoryDump invocations is achieved by // means of subsequent PostTask(s). // // 1) Prologue: // - Check if the dump provider is disabled, if so skip the dump. // - Check if we are on the right thread. If not hop and continue there. // 2) Invoke the dump provider's OnMemoryDump() (unless skipped). // 3) Epilogue: // - Unregister the dump provider if it failed too many times consecutively. // - Advance the |next_dump_provider| iterator to the next dump provider. // - If this was the last hop, create a trace event, add it to the trace // and finalize (invoke callback). void MemoryDumpManager::ContinueAsyncProcessDump( scoped_ptr pmd_async_state) { // 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(); const uint64_t dump_guid = pmd_async_state->req_args.dump_guid; const char* dump_provider_name = nullptr; // Pid of the target process being dumped. Often kNullProcessId (= current // process), non-zero when the coordinator process creates dumps on behalf // of child processes (see crbug.com/461788). ProcessId pid; // DO NOT put any LOG() statement in the locked sections, as in some contexts // (GPU process) LOG() ends up performing PostTask/IPCs. MemoryDumpProvider* mdp; bool skip_dump = false; { AutoLock lock(lock_); auto mdp_info = pmd_async_state->next_dump_provider; mdp = mdp_info->dump_provider; dump_provider_name = mdp_info->name; pid = mdp_info->options.target_pid; // If the dump provider did not specify a thread affinity, dump on // |dump_thread_|. SingleThreadTaskRunner* task_runner = mdp_info->task_runner.get(); if (!task_runner) task_runner = pmd_async_state->dump_thread_task_runner.get(); // |dump_thread_| might have been Stop()-ed at this point (if tracing was // disabled in the meanwhile). In such case the PostTask() below will fail. // |task_runner|, however, should always be non-null. DCHECK(task_runner); if (mdp_info->disabled || mdp_info->unregistered) { skip_dump = true; } else if (!task_runner->BelongsToCurrentThread()) { // It's time to hop onto another thread. // Copy the callback + arguments just for the unlikley case in which // PostTask fails. In such case the Bind helper will destroy the // pmd_async_state and we must keep a copy of the fields to notify the // abort. MemoryDumpCallback callback = pmd_async_state->callback; scoped_refptr callback_task_runner = pmd_async_state->callback_task_runner; const bool did_post_task = task_runner->PostTask( FROM_HERE, Bind(&MemoryDumpManager::ContinueAsyncProcessDump, Unretained(this), Passed(&pmd_async_state))); if (did_post_task) return; // The thread is gone. At this point the best thing we can do is to // disable the dump provider and abort this dump. mdp_info->disabled = true; return AbortDumpLocked(callback, callback_task_runner, dump_guid); } } // AutoLock(lock_) // Invoke the dump provider without holding the |lock_|. bool finalize = false; bool dump_successful = false; if (!skip_dump) { TRACE_EVENT_WITH_FLOW1(kTraceCategory, "MemoryDumpManager::ContinueAsyncProcessDump", TRACE_ID_MANGLE(dump_guid), TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "dump_provider.name", dump_provider_name); MemoryDumpArgs args = {pmd_async_state->req_args.level_of_detail}; ProcessMemoryDump* process_memory_dump = pmd_async_state->GetOrCreateMemoryDumpContainerForProcess(pid); dump_successful = mdp->OnMemoryDump(args, process_memory_dump); } { AutoLock lock(lock_); auto mdp_info = pmd_async_state->next_dump_provider; if (dump_successful) { mdp_info->consecutive_failures = 0; } else if (!skip_dump) { ++mdp_info->consecutive_failures; if (mdp_info->consecutive_failures >= kMaxConsecutiveFailuresCount) { mdp_info->disabled = true; } } ++pmd_async_state->next_dump_provider; finalize = pmd_async_state->next_dump_provider == dump_providers_.end(); if (mdp_info->unregistered) dump_providers_.erase(mdp_info); } if (!skip_dump && !dump_successful) { LOG(ERROR) << "MemoryDumpProvider \"" << dump_provider_name << "\" failed, " << "possibly due to sandboxing (crbug.com/461788)." << "Disabling dumper for current process. Try --no-sandbox."; } if (finalize) return FinalizeDumpAndAddToTrace(std::move(pmd_async_state)); ContinueAsyncProcessDump(std::move(pmd_async_state)); } // static void MemoryDumpManager::FinalizeDumpAndAddToTrace( scoped_ptr pmd_async_state) { const uint64_t dump_guid = pmd_async_state->req_args.dump_guid; if (!pmd_async_state->callback_task_runner->BelongsToCurrentThread()) { scoped_refptr callback_task_runner = pmd_async_state->callback_task_runner; callback_task_runner->PostTask( FROM_HERE, Bind(&MemoryDumpManager::FinalizeDumpAndAddToTrace, Passed(&pmd_async_state))); return; } TRACE_EVENT_WITH_FLOW0(kTraceCategory, "MemoryDumpManager::FinalizeDumpAndAddToTrace", TRACE_ID_MANGLE(dump_guid), TRACE_EVENT_FLAG_FLOW_IN); for (const auto& kv : pmd_async_state->process_dumps) { ProcessId pid = kv.first; // kNullProcessId for the current process. ProcessMemoryDump* process_memory_dump = kv.second.get(); TracedValue* traced_value = new TracedValue(); scoped_refptr event_value(traced_value); process_memory_dump->AsValueInto(traced_value); traced_value->SetString("level_of_detail", MemoryDumpLevelOfDetailToString( pmd_async_state->req_args.level_of_detail)); const char* const event_name = MemoryDumpTypeToString(pmd_async_state->req_args.dump_type); TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_PROCESS_ID( TRACE_EVENT_PHASE_MEMORY_DUMP, TraceLog::GetCategoryGroupEnabled(kTraceCategory), event_name, dump_guid, pid, kTraceEventNumArgs, kTraceEventArgNames, kTraceEventArgTypes, nullptr /* arg_values */, &event_value, TRACE_EVENT_FLAG_HAS_ID); } if (!pmd_async_state->callback.is_null()) { pmd_async_state->callback.Run(dump_guid, true /* success */); pmd_async_state->callback.Reset(); } TRACE_EVENT_NESTABLE_ASYNC_END0(kTraceCategory, "ProcessMemoryDump", TRACE_ID_MANGLE(dump_guid)); } // static void MemoryDumpManager::AbortDumpLocked( MemoryDumpCallback callback, scoped_refptr task_runner, uint64_t dump_guid) { if (callback.is_null()) return; // There is nothing to NACK. // Post the callback even if we are already on the right thread to avoid // invoking the callback while holding the lock_. task_runner->PostTask(FROM_HERE, Bind(callback, dump_guid, false /* success */)); } void MemoryDumpManager::OnTraceLogEnabled() { bool enabled; TRACE_EVENT_CATEGORY_GROUP_ENABLED(kTraceCategory, &enabled); if (!enabled) return; // Initialize the TraceLog for the current thread. This is to avoid that the // TraceLog memory dump provider is registered lazily in the PostTask() below // while the |lock_| is taken; TraceLog::GetInstance()->InitializeThreadLocalEventBufferIfSupported(); // Spin-up the thread used to invoke unbound dump providers. scoped_ptr dump_thread(new Thread("MemoryInfra")); if (!dump_thread->Start()) { LOG(ERROR) << "Failed to start the memory-infra thread for tracing"; return; } AutoLock lock(lock_); DCHECK(delegate_); // At this point we must have a delegate. scoped_refptr stack_frame_deduplicator = nullptr; scoped_refptr type_name_deduplicator = nullptr; if (heap_profiling_enabled_) { // If heap profiling is enabled, the stack frame deduplicator and type name // deduplicator will be in use. Add a metadata events to write the frames // and type IDs. stack_frame_deduplicator = new StackFrameDeduplicator; type_name_deduplicator = new TypeNameDeduplicator; TRACE_EVENT_API_ADD_METADATA_EVENT( "stackFrames", "stackFrames", scoped_refptr(stack_frame_deduplicator)); TRACE_EVENT_API_ADD_METADATA_EVENT( "typeNames", "typeNames", scoped_refptr(type_name_deduplicator)); } DCHECK(!dump_thread_); dump_thread_ = std::move(dump_thread); session_state_ = new MemoryDumpSessionState(stack_frame_deduplicator, type_name_deduplicator); for (auto it = dump_providers_.begin(); it != dump_providers_.end(); ++it) { it->disabled = false; it->consecutive_failures = 0; } subtle::NoBarrier_Store(&memory_tracing_enabled_, 1); // TODO(primiano): This is a temporary hack to disable periodic memory dumps // when running memory benchmarks until telemetry uses TraceConfig to // enable/disable periodic dumps. See crbug.com/529184 . if (!is_coordinator_ || CommandLine::ForCurrentProcess()->HasSwitch( "enable-memory-benchmarking")) { return; } // Enable periodic dumps. At the moment the periodic support is limited to at // most one low-detail periodic dump and at most one high-detail periodic // dump. If both are specified the high-detail period must be an integer // multiple of the low-level one. g_periodic_dumps_count = 0; const TraceConfig trace_config = TraceLog::GetInstance()->GetCurrentTraceConfig(); const TraceConfig::MemoryDumpConfig& config_list = trace_config.memory_dump_config(); if (config_list.empty()) return; uint32_t min_timer_period_ms = std::numeric_limits::max(); uint32_t heavy_dump_period_ms = 0; DCHECK_LE(config_list.size(), 2u); for (const TraceConfig::MemoryDumpTriggerConfig& config : config_list) { DCHECK(config.periodic_interval_ms); if (config.level_of_detail == MemoryDumpLevelOfDetail::DETAILED) heavy_dump_period_ms = config.periodic_interval_ms; min_timer_period_ms = std::min(min_timer_period_ms, config.periodic_interval_ms); } DCHECK_EQ(0u, heavy_dump_period_ms % min_timer_period_ms); g_heavy_dumps_rate = heavy_dump_period_ms / min_timer_period_ms; periodic_dump_timer_.Start(FROM_HERE, TimeDelta::FromMilliseconds(min_timer_period_ms), base::Bind(&RequestPeriodicGlobalDump)); } void MemoryDumpManager::OnTraceLogDisabled() { subtle::NoBarrier_Store(&memory_tracing_enabled_, 0); scoped_ptr dump_thread; { AutoLock lock(lock_); dump_thread = std::move(dump_thread_); session_state_ = nullptr; } // Thread stops are blocking and must be performed outside of the |lock_| // or will deadlock (e.g., if ContinueAsyncProcessDump() tries to acquire it). periodic_dump_timer_.Stop(); if (dump_thread) dump_thread->Stop(); } uint64_t MemoryDumpManager::GetTracingProcessId() const { return delegate_->GetTracingProcessId(); } MemoryDumpManager::MemoryDumpProviderInfo::MemoryDumpProviderInfo( MemoryDumpProvider* dump_provider, const char* name, const scoped_refptr& task_runner, const MemoryDumpProvider::Options& options) : dump_provider(dump_provider), name(name), task_runner(task_runner), options(options), consecutive_failures(0), disabled(false), unregistered(false) {} MemoryDumpManager::MemoryDumpProviderInfo::~MemoryDumpProviderInfo() {} bool MemoryDumpManager::MemoryDumpProviderInfo::operator<( const MemoryDumpProviderInfo& other) const { if (task_runner == other.task_runner) return dump_provider < other.dump_provider; // Ensure that unbound providers (task_runner == nullptr) always run last. return !(task_runner < other.task_runner); } MemoryDumpManager::ProcessMemoryDumpAsyncState::ProcessMemoryDumpAsyncState( MemoryDumpRequestArgs req_args, MemoryDumpProviderInfoSet::iterator next_dump_provider, const scoped_refptr& session_state, MemoryDumpCallback callback, const scoped_refptr& dump_thread_task_runner) : req_args(req_args), next_dump_provider(next_dump_provider), session_state(session_state), callback(callback), callback_task_runner(MessageLoop::current()->task_runner()), dump_thread_task_runner(dump_thread_task_runner) {} MemoryDumpManager::ProcessMemoryDumpAsyncState::~ProcessMemoryDumpAsyncState() { } ProcessMemoryDump* MemoryDumpManager::ProcessMemoryDumpAsyncState:: GetOrCreateMemoryDumpContainerForProcess(ProcessId pid) { auto iter = process_dumps.find(pid); if (iter == process_dumps.end()) { scoped_ptr new_pmd(new ProcessMemoryDump(session_state)); iter = process_dumps.insert(std::make_pair(pid, std::move(new_pmd))).first; } return iter->second.get(); } } // namespace trace_event } // namespace base