diff options
author | jamescook@chromium.org <jamescook@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-07-24 18:38:29 +0000 |
---|---|---|
committer | jamescook@chromium.org <jamescook@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-07-24 18:38:29 +0000 |
commit | 111494e2d84d16764a5a6b1f70c096932ead454e (patch) | |
tree | 5a81c9ba1236b2801305e7865094e0749e1f2cd4 | |
parent | b544c1ae28772fc1392a815af620433d59293363 (diff) | |
download | chromium_src-111494e2d84d16764a5a6b1f70c096932ead454e.zip chromium_src-111494e2d84d16764a5a6b1f70c096932ead454e.tar.gz chromium_src-111494e2d84d16764a5a6b1f70c096932ead454e.tar.bz2 |
Record Chrome trace events in tcmalloc heap profiles
This allows about:tracing to show tcmalloc heap memory allocation over time.
The implementation:
* Adds a "memory" checkbox to about:tracing
* Uses thread-local-storage to store the "stack" of trace events per thread while about:tracing is running.
* Introduces a StackGeneratorFunction callback into tcmalloc, allowing it to call back into Chrome to get a "stack" of trace events.
* Parses the heap profiler output of tcmalloc and converts it to JSON to be displayed by a visualizer in the about:tracing trace viewer.
BUG=243895
TEST=base_unittests TraceMemoryTest.*
Review URL: https://chromiumcodereview.appspot.com/15418002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@213473 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | base/base.gyp | 1 | ||||
-rw-r--r-- | base/base.gypi | 2 | ||||
-rw-r--r-- | base/debug/trace_event.h | 12 | ||||
-rw-r--r-- | base/debug/trace_event_impl.cc | 11 | ||||
-rw-r--r-- | base/debug/trace_event_impl.h | 1 | ||||
-rw-r--r-- | base/debug/trace_event_memory.cc | 414 | ||||
-rw-r--r-- | base/debug/trace_event_memory.h | 152 | ||||
-rw-r--r-- | base/debug/trace_event_memory_unittest.cc | 211 | ||||
-rw-r--r-- | content/browser/browser_main_loop.cc | 14 | ||||
-rw-r--r-- | content/browser/browser_main_loop.h | 10 | ||||
-rw-r--r-- | content/child/child_thread.cc | 12 | ||||
-rw-r--r-- | content/child/child_thread.h | 14 | ||||
-rw-r--r-- | third_party/tcmalloc/README.chromium | 2 | ||||
-rw-r--r-- | third_party/tcmalloc/chromium/src/gperftools/heap-profiler.h | 15 | ||||
-rw-r--r-- | third_party/tcmalloc/chromium/src/heap-profiler.cc | 21 |
15 files changed, 880 insertions, 12 deletions
diff --git a/base/base.gyp b/base/base.gyp index 56a036a..6f22d5a 100644 --- a/base/base.gyp +++ b/base/base.gyp @@ -469,6 +469,7 @@ 'debug/leak_tracker_unittest.cc', 'debug/proc_maps_linux_unittest.cc', 'debug/stack_trace_unittest.cc', + 'debug/trace_event_memory_unittest.cc', 'debug/trace_event_unittest.cc', 'debug/trace_event_unittest.h', 'debug/trace_event_win_unittest.cc', diff --git a/base/base.gypi b/base/base.gypi index 02abd4b..5085018 100644 --- a/base/base.gypi +++ b/base/base.gypi @@ -148,6 +148,8 @@ 'debug/trace_event_impl.cc', 'debug/trace_event_impl.h', 'debug/trace_event_impl_constants.cc', + 'debug/trace_event_memory.cc', + 'debug/trace_event_memory.h', 'debug/trace_event_win.cc', 'deferred_sequenced_task_runner.cc', 'deferred_sequenced_task_runner.h', diff --git a/base/debug/trace_event.h b/base/debug/trace_event.h index c17c008..f6a903d 100644 --- a/base/debug/trace_event.h +++ b/base/debug/trace_event.h @@ -193,6 +193,7 @@ #include "base/atomicops.h" #include "base/debug/trace_event_impl.h" +#include "base/debug/trace_event_memory.h" #include "build/build_config.h" // By default, const char* argument values are assumed to have long-lived scope @@ -220,13 +221,16 @@ // - category and name strings must have application lifetime (statics or // literals). They may not include " chars. #define TRACE_EVENT0(category_group, name) \ + INTERNAL_TRACE_MEMORY(category_group, name) \ INTERNAL_TRACE_EVENT_ADD_SCOPED(category_group, name) #define TRACE_EVENT1(category_group, name, arg1_name, arg1_val) \ + INTERNAL_TRACE_MEMORY(category_group, name) \ INTERNAL_TRACE_EVENT_ADD_SCOPED(category_group, name, arg1_name, arg1_val) -#define TRACE_EVENT2(category_group, name, arg1_name, arg1_val, arg2_name, \ - arg2_val) \ - INTERNAL_TRACE_EVENT_ADD_SCOPED(category_group, name, arg1_name, arg1_val, \ - arg2_name, arg2_val) +#define TRACE_EVENT2( \ + category_group, name, arg1_name, arg1_val, arg2_name, arg2_val) \ + INTERNAL_TRACE_MEMORY(category_group, name) \ + INTERNAL_TRACE_EVENT_ADD_SCOPED( \ + category_group, name, arg1_name, arg1_val, arg2_name, arg2_val) // UNSHIPPED_TRACE_EVENT* are like TRACE_EVENT* except that they are not // included in official builds. diff --git a/base/debug/trace_event_impl.cc b/base/debug/trace_event_impl.cc index e061ea1..3b92646 100644 --- a/base/debug/trace_event_impl.cc +++ b/base/debug/trace_event_impl.cc @@ -1083,6 +1083,14 @@ void TraceLog::RemoveEnabledStateObserver(EnabledStateObserver* listener) { enabled_state_observer_list_.erase(it); } +bool TraceLog::HasEnabledStateObserver(EnabledStateObserver* listener) const { + std::vector<EnabledStateObserver*>::const_iterator it = + std::find(enabled_state_observer_list_.begin(), + enabled_state_observer_list_.end(), + listener); + return it != enabled_state_observer_list_.end(); +} + float TraceLog::GetBufferPercentFull() const { return (float)((double)logged_events_->Size()/(double)kTraceEventBufferSize); } @@ -1107,6 +1115,9 @@ void TraceLog::SetEventCallback(EventCallback cb) { }; void TraceLog::Flush(const TraceLog::OutputCallback& cb) { + // Ignore memory allocations from here down. + INTERNAL_TRACE_MEMORY(TRACE_DISABLED_BY_DEFAULT("memory"), + TRACE_MEMORY_IGNORE); scoped_ptr<TraceBuffer> previous_logged_events; { AutoLock lock(lock_); diff --git a/base/debug/trace_event_impl.h b/base/debug/trace_event_impl.h index 0449090b..0f30c1e 100644 --- a/base/debug/trace_event_impl.h +++ b/base/debug/trace_event_impl.h @@ -345,6 +345,7 @@ class BASE_EXPORT TraceLog { }; void AddEnabledStateObserver(EnabledStateObserver* listener); void RemoveEnabledStateObserver(EnabledStateObserver* listener); + bool HasEnabledStateObserver(EnabledStateObserver* listener) const; float GetBufferPercentFull() const; diff --git a/base/debug/trace_event_memory.cc b/base/debug/trace_event_memory.cc new file mode 100644 index 0000000..fb5f65b --- /dev/null +++ b/base/debug/trace_event_memory.cc @@ -0,0 +1,414 @@ +// Copyright 2013 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/debug/trace_event_memory.h" + +#include "base/debug/leak_annotations.h" +#include "base/debug/trace_event.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/threading/thread_local_storage.h" + +namespace base { +namespace debug { + +namespace { + +// Maximum number of nested TRACE_MEMORY scopes to record. Must be greater than +// or equal to HeapProfileTable::kMaxStackDepth. +const size_t kMaxStackSize = 32; + +///////////////////////////////////////////////////////////////////////////// +// Holds a memory dump until the tracing system needs to serialize it. +class MemoryDumpHolder : public base::debug::ConvertableToTraceFormat { + public: + // Takes ownership of dump, which must be a JSON string, allocated with + // malloc() and NULL terminated. + explicit MemoryDumpHolder(char* dump) : dump_(dump) {} + virtual ~MemoryDumpHolder() { free(dump_); } + + // base::debug::ConvertableToTraceFormat overrides: + virtual void AppendAsTraceFormat(std::string* out) const OVERRIDE { + AppendHeapProfileAsTraceFormat(dump_, out); + } + + private: + char* dump_; + + DISALLOW_COPY_AND_ASSIGN(MemoryDumpHolder); +}; + +///////////////////////////////////////////////////////////////////////////// +// Records a stack of TRACE_MEMORY events. One per thread is required. +struct TraceMemoryStack { + TraceMemoryStack() : index_(0) { + memset(category_stack_, 0, kMaxStackSize * sizeof(category_stack_[0])); + } + + // Points to the next free entry. + size_t index_; + const char* category_stack_[kMaxStackSize]; +}; + +// Pointer to a TraceMemoryStack per thread. +base::ThreadLocalStorage::StaticSlot tls_trace_memory_stack = TLS_INITIALIZER; + +// Clean up memory pointed to by our thread-local storage. +void DeleteStackOnThreadCleanup(void* value) { + TraceMemoryStack* stack = static_cast<TraceMemoryStack*>(value); + delete stack; +} + +// Initializes the thread-local TraceMemoryStack pointer. Returns true on +// success or if it is already initialized. +bool InitThreadLocalStorage() { + if (tls_trace_memory_stack.initialized()) + return true; + // Initialize the thread-local storage key, returning true on success. + return tls_trace_memory_stack.Initialize(&DeleteStackOnThreadCleanup); +} + +// Clean up thread-local-storage in the main thread. +void CleanupThreadLocalStorage() { + if (!tls_trace_memory_stack.initialized()) + return; + TraceMemoryStack* stack = + static_cast<TraceMemoryStack*>(tls_trace_memory_stack.Get()); + delete stack; + tls_trace_memory_stack.Set(NULL); + // Intentionally do not release the thread-local-storage key here, that is, + // do not call tls_trace_memory_stack.Free(). Other threads have lazily + // created pointers in thread-local-storage via GetTraceMemoryStack() below. + // Those threads need to run the DeleteStack() destructor function when they + // exit. If we release the key the destructor will not be called and those + // threads will not clean up their memory. +} + +// Returns the thread-local trace memory stack for the current thread, creating +// one if needed. Returns NULL if the thread-local storage key isn't +// initialized, which indicates that heap profiling isn't running. +TraceMemoryStack* GetTraceMemoryStack() { + TraceMemoryStack* stack = + static_cast<TraceMemoryStack*>(tls_trace_memory_stack.Get()); + // Lazily initialize TraceMemoryStack objects for new threads. + if (!stack) { + stack = new TraceMemoryStack; + tls_trace_memory_stack.Set(stack); + } + return stack; +} + +// Returns a "pseudo-stack" of pointers to trace events. +// TODO(jamescook): Record both category and name, perhaps in a pair for speed. +int GetPseudoStack(int skip_count_ignored, void** stack_out) { + // If the tracing system isn't fully initialized, just skip this allocation. + // Attempting to initialize will allocate memory, causing this function to + // be called recursively from inside the allocator. + if (!tls_trace_memory_stack.initialized() || !tls_trace_memory_stack.Get()) + return 0; + TraceMemoryStack* stack = + static_cast<TraceMemoryStack*>(tls_trace_memory_stack.Get()); + // Copy at most kMaxStackSize stack entries. + const size_t count = std::min(stack->index_, kMaxStackSize); + // Notes that memcpy() works for zero bytes. + memcpy(stack_out, + stack->category_stack_, + count * sizeof(stack->category_stack_[0])); + // Function must return an int to match the signature required by tcmalloc. + return static_cast<int>(count); +} + +} // namespace + +////////////////////////////////////////////////////////////////////////////// + +TraceMemoryController::TraceMemoryController( + scoped_refptr<MessageLoopProxy> message_loop_proxy, + HeapProfilerStartFunction heap_profiler_start_function, + HeapProfilerStopFunction heap_profiler_stop_function, + GetHeapProfileFunction get_heap_profile_function) + : message_loop_proxy_(message_loop_proxy), + heap_profiler_start_function_(heap_profiler_start_function), + heap_profiler_stop_function_(heap_profiler_stop_function), + get_heap_profile_function_(get_heap_profile_function), + weak_factory_(this) { + // Force the "memory" category to show up in the trace viewer. + TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("memory"), "init"); + // Watch for the tracing system being enabled. + TraceLog::GetInstance()->AddEnabledStateObserver(this); +} + +TraceMemoryController::~TraceMemoryController() { + if (dump_timer_.IsRunning()) + StopProfiling(); + TraceLog::GetInstance()->RemoveEnabledStateObserver(this); +} + + // base::debug::TraceLog::EnabledStateChangedObserver overrides: +void TraceMemoryController::OnTraceLogEnabled() { + // Check to see if tracing is enabled for the memory category. + bool enabled; + TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT("memory"), + &enabled); + if (!enabled) + return; + DVLOG(1) << "OnTraceLogEnabled"; + message_loop_proxy_->PostTask( + FROM_HERE, + base::Bind(&TraceMemoryController::StartProfiling, + weak_factory_.GetWeakPtr())); +} + +void TraceMemoryController::OnTraceLogDisabled() { + // The memory category is always disabled before OnTraceLogDisabled() is + // called, so we cannot tell if it was enabled before. Always try to turn + // off profiling. + DVLOG(1) << "OnTraceLogDisabled"; + message_loop_proxy_->PostTask( + FROM_HERE, + base::Bind(&TraceMemoryController::StopProfiling, + weak_factory_.GetWeakPtr())); +} + +void TraceMemoryController::StartProfiling() { + // Watch for the tracing framework sending enabling more than once. + if (dump_timer_.IsRunning()) + return; + DVLOG(1) << "Starting trace memory"; + if (!InitThreadLocalStorage()) + return; + ScopedTraceMemory::set_enabled(true); + // Call ::HeapProfilerWithPseudoStackStart(). + heap_profiler_start_function_(&GetPseudoStack); + const int kDumpIntervalSeconds = 5; + dump_timer_.Start(FROM_HERE, + TimeDelta::FromSeconds(kDumpIntervalSeconds), + base::Bind(&TraceMemoryController::DumpMemoryProfile, + weak_factory_.GetWeakPtr())); +} + +void TraceMemoryController::DumpMemoryProfile() { + // Don't trace allocations here in the memory tracing system. + INTERNAL_TRACE_MEMORY(TRACE_DISABLED_BY_DEFAULT("memory"), + TRACE_MEMORY_IGNORE); + + DVLOG(1) << "DumpMemoryProfile"; + // MemoryDumpHolder takes ownership of this string. See GetHeapProfile() in + // tcmalloc for details. + char* dump = get_heap_profile_function_(); + scoped_ptr<MemoryDumpHolder> dump_holder(new MemoryDumpHolder(dump)); + const int kSnapshotId = 1; + TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID( + TRACE_DISABLED_BY_DEFAULT("memory"), + "memory::Heap", + kSnapshotId, + dump_holder.PassAs<base::debug::ConvertableToTraceFormat>()); +} + +void TraceMemoryController::StopProfiling() { + // Watch for the tracing framework sending disabled more than once. + if (!dump_timer_.IsRunning()) + return; + DVLOG(1) << "Stopping trace memory"; + dump_timer_.Stop(); + ScopedTraceMemory::set_enabled(false); + CleanupThreadLocalStorage(); + // Call ::HeapProfilerStop(). + heap_profiler_stop_function_(); +} + +bool TraceMemoryController::IsTimerRunningForTest() const { + return dump_timer_.IsRunning(); +} + +///////////////////////////////////////////////////////////////////////////// + +// static +bool ScopedTraceMemory::enabled_ = false; + +ScopedTraceMemory::ScopedTraceMemory(const char* category) { + // Not enabled indicates that the trace system isn't running, so don't + // record anything. + if (!enabled_) + return; + // Get our thread's copy of the stack. + TraceMemoryStack* trace_memory_stack = GetTraceMemoryStack(); + const size_t index = trace_memory_stack->index_; + // Allow deep nesting of stacks (needed for tests), but only record + // |kMaxStackSize| entries. + if (index < kMaxStackSize) + trace_memory_stack->category_stack_[index] = category; + trace_memory_stack->index_++; +} + +ScopedTraceMemory::~ScopedTraceMemory() { + // Not enabled indicates that the trace system isn't running, so don't + // record anything. + if (!enabled_) + return; + // Get our thread's copy of the stack. + TraceMemoryStack* trace_memory_stack = GetTraceMemoryStack(); + // The tracing system can be turned on with ScopedTraceMemory objects + // allocated on the stack, so avoid potential underflow as they are destroyed. + if (trace_memory_stack->index_ > 0) + trace_memory_stack->index_--; +} + +// static +void ScopedTraceMemory::InitForTest() { + InitThreadLocalStorage(); + enabled_ = true; +} + +// static +void ScopedTraceMemory::CleanupForTest() { + enabled_ = false; + CleanupThreadLocalStorage(); +} + +// static +int ScopedTraceMemory::GetStackIndexForTest() { + TraceMemoryStack* stack = GetTraceMemoryStack(); + return static_cast<int>(stack->index_); +} + +// static +const char* ScopedTraceMemory::GetItemForTest(int index) { + TraceMemoryStack* stack = GetTraceMemoryStack(); + return stack->category_stack_[index]; +} + +///////////////////////////////////////////////////////////////////////////// + +void AppendHeapProfileAsTraceFormat(const char* input, std::string* output) { + // Heap profile output has a header total line, then a list of stacks with + // memory totals, like this: + // + // heap profile: 357: 55227 [ 14653: 2624014] @ heapprofile + // 95: 40940 [ 649: 114260] @ 0x7fa7f4b3be13 + // 77: 32546 [ 742: 106234] @ + // 68: 4195 [ 1087: 98009] @ 0x7fa7fa9b9ba0 0x7fa7f4b3be13 + // + // MAPPED_LIBRARIES: + // 1be411fc1000-1be4139e4000 rw-p 00000000 00:00 0 + // 1be4139e4000-1be4139e5000 ---p 00000000 00:00 0 + // ... + // + // Skip input after MAPPED_LIBRARIES. + std::string input_string; + const char* mapped_libraries = strstr(input, "MAPPED_LIBRARIES"); + if (mapped_libraries) { + input_string.assign(input, mapped_libraries - input); + } else { + input_string.assign(input); + } + + std::vector<std::string> lines; + size_t line_count = Tokenize(input_string, "\n", &lines); + if (line_count == 0) { + DLOG(WARNING) << "No lines found"; + return; + } + + // Handle the initial summary line. + output->append("["); + AppendHeapProfileTotalsAsTraceFormat(lines[0], output); + + // Handle the following stack trace lines. + for (size_t i = 1; i < line_count; ++i) { + const std::string& line = lines[i]; + AppendHeapProfileLineAsTraceFormat(line, output); + } + output->append("]\n"); +} + +void AppendHeapProfileTotalsAsTraceFormat(const std::string& line, + std::string* output) { + // This is what a line looks like: + // heap profile: 357: 55227 [ 14653: 2624014] @ heapprofile + // + // The numbers represent total allocations since profiling was enabled. + // From the example above: + // 357 = Outstanding allocations (mallocs - frees) + // 55227 = Outstanding bytes (malloc bytes - free bytes) + // 14653 = Total allocations (mallocs) + // 2624014 = Total bytes (malloc bytes) + std::vector<std::string> tokens; + Tokenize(line, " :[]@", &tokens); + if (tokens.size() < 4) { + DLOG(WARNING) << "Invalid totals line " << line; + return; + } + DCHECK_EQ(tokens[0], "heap"); + DCHECK_EQ(tokens[1], "profile"); + output->append("{\"current_allocs\": "); + output->append(tokens[2]); + output->append(", \"current_bytes\": "); + output->append(tokens[3]); + output->append(", \"trace\": \"\"}"); +} + +bool AppendHeapProfileLineAsTraceFormat(const std::string& line, + std::string* output) { + // This is what a line looks like: + // 68: 4195 [ 1087: 98009] @ 0x7fa7fa9b9ba0 0x7fa7f4b3be13 + // + // The numbers represent allocations for a particular stack trace since + // profiling was enabled. From the example above: + // 68 = Outstanding allocations (mallocs - frees) + // 4195 = Outstanding bytes (malloc bytes - free bytes) + // 1087 = Total allocations (mallocs) + // 98009 = Total bytes (malloc bytes) + // + // 0x7fa7fa9b9ba0 0x7fa7f4b3be13 = Stack trace represented as pointers to + // static strings from trace event names. + std::vector<std::string> tokens; + Tokenize(line, " :[]@", &tokens); + // It's valid to have no stack addresses, so only require 4 tokens. + if (tokens.size() < 4) { + DLOG(WARNING) << "Invalid line " << line; + return false; + } + // Don't bother with stacks that have no current allocations. + if (tokens[0] == "0") + return false; + output->append(",\n"); + output->append("{\"current_allocs\": "); + output->append(tokens[0]); + output->append(", \"current_bytes\": "); + output->append(tokens[1]); + output->append(", \"trace\": \""); + + // Convert the "stack addresses" into strings. + const std::string kSingleQuote = "'"; + for (size_t t = 4; t < tokens.size(); ++t) { + // Each stack address is a pointer to a constant trace name string. + uint64 address = 0; + if (!base::HexStringToUInt64(tokens[t], &address)) + break; + // This is ugly but otherwise tcmalloc would need to gain a special output + // serializer for pseudo-stacks. Note that this cast also handles 64-bit to + // 32-bit conversion if necessary. Tests use a null address. + const char* trace_name = + address ? reinterpret_cast<const char*>(address) : "null"; + + // Some trace name strings have double quotes, convert them to single. + std::string trace_name_string(trace_name); + ReplaceChars(trace_name_string, "\"", kSingleQuote, &trace_name_string); + + output->append(trace_name_string); + + // Trace viewer expects a trailing space. + output->append(" "); + } + output->append("\"}"); + return true; +} + +} // namespace debug +} // namespace base diff --git a/base/debug/trace_event_memory.h b/base/debug/trace_event_memory.h new file mode 100644 index 0000000..0d82198 --- /dev/null +++ b/base/debug/trace_event_memory.h @@ -0,0 +1,152 @@ +// Copyright 2013 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. + +#ifndef BASE_DEBUG_TRACE_EVENT_MEMORY_H_ +#define BASE_DEBUG_TRACE_EVENT_MEMORY_H_ + +#include "base/base_export.h" +#include "base/debug/trace_event_impl.h" +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/timer/timer.h" + +// TODO(jamescook): Windows support for memory tracing. +#if !defined(NO_TCMALLOC) && !defined(OS_NACL) && \ + (defined(OS_LINUX) || defined(OS_ANDROID)) +#define TCMALLOC_TRACE_MEMORY_SUPPORTED 1 +#endif + +namespace base { + +class MessageLoopProxy; + +namespace debug { + +// Watches for chrome://tracing to be enabled or disabled. When tracing is +// enabled, also enables tcmalloc heap profiling. This class is the preferred +// way to turn trace-base heap memory profiling on and off. +class BASE_EXPORT TraceMemoryController + : public TraceLog::EnabledStateObserver { + public: + typedef int (*StackGeneratorFunction)(int skip_count, void** stack); + typedef void (*HeapProfilerStartFunction)(StackGeneratorFunction callback); + typedef void (*HeapProfilerStopFunction)(); + typedef char* (*GetHeapProfileFunction)(); + + // |message_loop_proxy| must be a proxy to the primary thread for the client + // process, e.g. the UI thread in a browser. The function pointers must be + // pointers to tcmalloc heap profiling functions; by avoiding direct calls to + // these functions we avoid a dependency on third_party/tcmalloc from base. + TraceMemoryController( + scoped_refptr<MessageLoopProxy> message_loop_proxy, + HeapProfilerStartFunction heap_profiler_start_function, + HeapProfilerStopFunction heap_profiler_stop_function, + GetHeapProfileFunction get_heap_profile_function); + virtual ~TraceMemoryController(); + + // base::debug::TraceLog::EnabledStateChangedObserver overrides: + virtual void OnTraceLogEnabled() OVERRIDE; + virtual void OnTraceLogDisabled() OVERRIDE; + + // Starts heap memory profiling. + void StartProfiling(); + + // Captures a heap profile. + void DumpMemoryProfile(); + + // If memory tracing is enabled, dumps a memory profile to the tracing system. + void StopProfiling(); + + private: + FRIEND_TEST_ALL_PREFIXES(TraceMemoryTest, TraceMemoryController); + + bool IsTimerRunningForTest() const; + + // Ensures the observer starts and stops tracing on the primary thread. + scoped_refptr<MessageLoopProxy> message_loop_proxy_; + + // Pointers to tcmalloc heap profiling functions. Allows this class to use + // tcmalloc functions without introducing a dependency from base to tcmalloc. + HeapProfilerStartFunction heap_profiler_start_function_; + HeapProfilerStopFunction heap_profiler_stop_function_; + GetHeapProfileFunction get_heap_profile_function_; + + // Timer to schedule memory profile dumps. + RepeatingTimer<TraceMemoryController> dump_timer_; + + WeakPtrFactory<TraceMemoryController> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(TraceMemoryController); +}; + +////////////////////////////////////////////////////////////////////////////// + +// A scoped context for memory tracing. Pushes the name onto a stack for +// recording by tcmalloc heap profiling. +class BASE_EXPORT ScopedTraceMemory { + public: + // Memory for |name| must be static, for example, a literal string in + // a TRACE_EVENT macro. + explicit ScopedTraceMemory(const char* name); + ~ScopedTraceMemory(); + + // Enables the storing of trace names on a per-thread stack. + static void set_enabled(bool enabled) { enabled_ = enabled; } + + // Testing interface: + static void InitForTest(); + static void CleanupForTest(); + static int GetStackIndexForTest(); + static const char* GetItemForTest(int index); + + private: + static bool enabled_; + DISALLOW_COPY_AND_ASSIGN(ScopedTraceMemory); +}; + +////////////////////////////////////////////////////////////////////////////// + +// Converts tcmalloc's heap profiler data with pseudo-stacks in |input| to +// trace event compatible JSON and appends to |output|. Visible for testing. +BASE_EXPORT void AppendHeapProfileAsTraceFormat(const char* input, + std::string* output); + +// Converts the first |line| of heap profiler data, which contains totals for +// all allocations in a special format, into trace event compatible JSON and +// appends to |output|. Visible for testing. +BASE_EXPORT void AppendHeapProfileTotalsAsTraceFormat(const std::string& line, + std::string* output); + +// Converts a single |line| of heap profiler data into trace event compatible +// JSON and appends to |output|. Returns true if the line was valid and has a +// non-zero number of current allocations. Visible for testing. +BASE_EXPORT bool AppendHeapProfileLineAsTraceFormat(const std::string& line, + std::string* output); + +} // namespace debug +} // namespace base + +// Make local variables with unique names based on the line number. Note that +// the extra level of redirection is needed. +#define INTERNAL_TRACE_MEMORY_ID3(line) trace_memory_unique_##line +#define INTERNAL_TRACE_MEMORY_ID2(line) INTERNAL_TRACE_MEMORY_ID3(line) +#define INTERNAL_TRACE_MEMORY_ID INTERNAL_TRACE_MEMORY_ID2(__LINE__) + +// This is the core macro that adds a scope to each TRACE_EVENT location. +// It generates a unique local variable name using the macros above. +// TODO(jamescook): Make it record both category and name. +#if defined(TCMALLOC_TRACE_MEMORY_SUPPORTED) +#define INTERNAL_TRACE_MEMORY(category, name) \ + base::debug::ScopedTraceMemory INTERNAL_TRACE_MEMORY_ID(name); +#else +#define INTERNAL_TRACE_MEMORY(category, name) +#endif // defined(TRACE_MEMORY_SUPPORTED) + +// A special trace name that allows us to ignore memory allocations inside +// the memory dump system itself. The allocations are recorded, but the +// visualizer skips them. Must match the value in heap.js. +#define TRACE_MEMORY_IGNORE "trace-memory-ignore" + +#endif // BASE_DEBUG_TRACE_EVENT_MEMORY_H_ diff --git a/base/debug/trace_event_memory_unittest.cc b/base/debug/trace_event_memory_unittest.cc new file mode 100644 index 0000000..7c4eae6 --- /dev/null +++ b/base/debug/trace_event_memory_unittest.cc @@ -0,0 +1,211 @@ +// Copyright 2013 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/debug/trace_event_memory.h" + +#include <sstream> +#include <string> + +#include "base/debug/trace_event_impl.h" +#include "base/message_loop/message_loop.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(TCMALLOC_TRACE_MEMORY_SUPPORTED) +#include "third_party/tcmalloc/chromium/src/gperftools/heap-profiler.h" +#endif + +namespace base { +namespace debug { + +// Tests for the trace event memory tracking system. Exists as a class so it +// can be a friend of TraceMemoryController. +class TraceMemoryTest : public testing::Test { + public: + TraceMemoryTest() {} + virtual ~TraceMemoryTest() {} + + private: + DISALLOW_COPY_AND_ASSIGN(TraceMemoryTest); +}; + +////////////////////////////////////////////////////////////////////////////// + +#if defined(TCMALLOC_TRACE_MEMORY_SUPPORTED) + +TEST_F(TraceMemoryTest, TraceMemoryController) { + MessageLoop message_loop; + + // Start with no observers of the TraceLog. + EXPECT_EQ(0u, TraceLog::GetInstance()->GetObserverCountForTest()); + + // Creating a controller adds it to the TraceLog observer list. + scoped_ptr<TraceMemoryController> controller( + new TraceMemoryController( + message_loop.message_loop_proxy(), + ::HeapProfilerWithPseudoStackStart, + ::HeapProfilerStop, + ::GetHeapProfile)); + EXPECT_EQ(1u, TraceLog::GetInstance()->GetObserverCountForTest()); + EXPECT_TRUE( + TraceLog::GetInstance()->HasEnabledStateObserver(controller.get())); + + // By default the observer isn't dumping memory profiles. + EXPECT_FALSE(controller->IsTimerRunningForTest()); + + // Simulate enabling tracing. + controller->StartProfiling(); + message_loop.RunUntilIdle(); + EXPECT_TRUE(controller->IsTimerRunningForTest()); + + // Simulate disabling tracing. + controller->StopProfiling(); + message_loop.RunUntilIdle(); + EXPECT_FALSE(controller->IsTimerRunningForTest()); + + // Deleting the observer removes it from the TraceLog observer list. + controller.reset(); + EXPECT_EQ(0u, TraceLog::GetInstance()->GetObserverCountForTest()); +} + +TEST_F(TraceMemoryTest, ScopedTraceMemory) { + ScopedTraceMemory::InitForTest(); + + // Start with an empty stack. + EXPECT_EQ(0, ScopedTraceMemory::GetStackIndexForTest()); + + { + // Push an item. + const char kScope1[] = "scope1"; + ScopedTraceMemory scope1(kScope1); + EXPECT_EQ(1, ScopedTraceMemory::GetStackIndexForTest()); + EXPECT_EQ(kScope1, ScopedTraceMemory::GetItemForTest(0)); + + { + // One more item. + const char kScope2[] = "scope2"; + ScopedTraceMemory scope2(kScope2); + EXPECT_EQ(2, ScopedTraceMemory::GetStackIndexForTest()); + EXPECT_EQ(kScope2, ScopedTraceMemory::GetItemForTest(1)); + } + + // Ended scope 2. + EXPECT_EQ(1, ScopedTraceMemory::GetStackIndexForTest()); + } + + // Ended scope 1. + EXPECT_EQ(0, ScopedTraceMemory::GetStackIndexForTest()); + + ScopedTraceMemory::CleanupForTest(); +} + +void TestDeepScopeNesting(int current, int depth) { + EXPECT_EQ(current, ScopedTraceMemory::GetStackIndexForTest()); + const char kCategory[] = "foo"; + ScopedTraceMemory scope(kCategory); + if (current < depth) + TestDeepScopeNesting(current + 1, depth); + EXPECT_EQ(current + 1, ScopedTraceMemory::GetStackIndexForTest()); +} + +TEST_F(TraceMemoryTest, DeepScopeNesting) { + ScopedTraceMemory::InitForTest(); + + // Ensure really deep scopes don't crash. + TestDeepScopeNesting(0, 100); + + ScopedTraceMemory::CleanupForTest(); +} + +#endif // defined(TRACE_MEMORY_SUPPORTED) + +///////////////////////////////////////////////////////////////////////////// + +TEST_F(TraceMemoryTest, AppendHeapProfileTotalsAsTraceFormat) { + // Empty input gives empty output. + std::string empty_output; + AppendHeapProfileTotalsAsTraceFormat("", &empty_output); + EXPECT_EQ("", empty_output); + + // Typical case. + const char input[] = + "heap profile: 357: 55227 [ 14653: 2624014] @ heapprofile"; + const std::string kExpectedOutput = + "{\"current_allocs\": 357, \"current_bytes\": 55227, \"trace\": \"\"}"; + std::string output; + AppendHeapProfileTotalsAsTraceFormat(input, &output); + EXPECT_EQ(kExpectedOutput, output); +} + +TEST_F(TraceMemoryTest, AppendHeapProfileLineAsTraceFormat) { + // Empty input gives empty output. + std::string empty_output; + EXPECT_FALSE(AppendHeapProfileLineAsTraceFormat("", &empty_output)); + EXPECT_EQ("", empty_output); + + // Invalid input returns false. + std::string junk_output; + EXPECT_FALSE(AppendHeapProfileLineAsTraceFormat("junk", &junk_output)); + + // Input with the addresses of name1 and name2. + const char kName1[] = "name1"; + const char kName2[] = "name2"; + std::ostringstream input; + input << " 68: 4195 [ 1087: 98009] @ " << &kName1 << " " << &kName2; + const std::string kExpectedOutput = + ",\n" + "{" + "\"current_allocs\": 68, " + "\"current_bytes\": 4195, " + "\"trace\": \"name1 name2 \"" + "}"; + std::string output; + EXPECT_TRUE( + AppendHeapProfileLineAsTraceFormat(input.str().c_str(), &output)); + EXPECT_EQ(kExpectedOutput, output); + + // Zero current allocations is skipped. + std::ostringstream zero_input; + zero_input << " 0: 0 [ 1087: 98009] @ " << &kName1 << " " + << &kName2; + std::string zero_output; + EXPECT_FALSE(AppendHeapProfileLineAsTraceFormat(zero_input.str().c_str(), + &zero_output)); + EXPECT_EQ("", zero_output); +} + +TEST_F(TraceMemoryTest, AppendHeapProfileAsTraceFormat) { + // Empty input gives empty output. + std::string empty_output; + AppendHeapProfileAsTraceFormat("", &empty_output); + EXPECT_EQ("", empty_output); + + // Typical case. + const char input[] = + "heap profile: 357: 55227 [ 14653: 2624014] @ heapprofile\n" + " 95: 40940 [ 649: 114260] @\n" + " 77: 32546 [ 742: 106234] @ 0x0 0x0\n" + " 0: 0 [ 132: 4236] @ 0x0\n" + "\n" + "MAPPED_LIBRARIES:\n" + "1be411fc1000-1be4139e4000 rw-p 00000000 00:00 0\n" + "1be4139e4000-1be4139e5000 ---p 00000000 00:00 0\n"; + const std::string kExpectedOutput = + "[{" + "\"current_allocs\": 357, " + "\"current_bytes\": 55227, " + "\"trace\": \"\"},\n" + "{\"current_allocs\": 95, " + "\"current_bytes\": 40940, " + "\"trace\": \"\"},\n" + "{\"current_allocs\": 77, " + "\"current_bytes\": 32546, " + "\"trace\": \"null null \"" + "}]\n"; + std::string output; + AppendHeapProfileAsTraceFormat(input, &output); + EXPECT_EQ(kExpectedOutput, output); +} + +} // namespace debug +} // namespace base diff --git a/content/browser/browser_main_loop.cc b/content/browser/browser_main_loop.cc index 241b7e5..d95dbb59 100644 --- a/content/browser/browser_main_loop.cc +++ b/content/browser/browser_main_loop.cc @@ -96,6 +96,10 @@ #include "content/browser/zygote_host/zygote_host_impl_linux.h" #endif +#if defined(TCMALLOC_TRACE_MEMORY_SUPPORTED) +#include "third_party/tcmalloc/chromium/src/gperftools/heap-profiler.h" +#endif + #if defined(USE_X11) #include <X11/Xlib.h> #endif @@ -469,6 +473,14 @@ void BrowserMainLoop::MainMessageLoopStart() { memory_observer_.reset(new MemoryObserver()); base::MessageLoop::current()->AddTaskObserver(memory_observer_.get()); } + +#if defined(TCMALLOC_TRACE_MEMORY_SUPPORTED) + trace_memory_controller_.reset(new base::debug::TraceMemoryController( + base::MessageLoop::current()->message_loop_proxy(), + ::HeapProfilerWithPseudoStackStart, + ::HeapProfilerStop, + ::GetHeapProfile)); +#endif } void BrowserMainLoop::CreateThreads() { @@ -629,6 +641,8 @@ void BrowserMainLoop::ShutdownThreadsAndCleanUp() { if (parts_) parts_->PostMainMessageLoopRun(); + trace_memory_controller_.reset(); + #if !defined(OS_IOS) // Destroying the GpuProcessHostUIShims on the UI thread posts a task to // delete related objects on the GPU thread. This must be done before diff --git a/content/browser/browser_main_loop.h b/content/browser/browser_main_loop.h index 0f3d3d7..5c3608b 100644 --- a/content/browser/browser_main_loop.h +++ b/content/browser/browser_main_loop.h @@ -16,16 +16,19 @@ class HighResolutionTimerManager; class MessageLoop; class PowerMonitor; class SystemMonitor; -} +namespace debug { +class TraceMemoryController; +} // namespace debug +} // namespace base namespace media { class AudioManager; class MIDIManager; -} +} // namespace media namespace net { class NetworkChangeNotifier; -} +} // namespace net namespace content { class AudioMirroringManager; @@ -142,6 +145,7 @@ class CONTENT_EXPORT BrowserMainLoop { scoped_ptr<BrowserProcessSubThread> io_thread_; scoped_ptr<base::Thread> indexed_db_thread_; scoped_ptr<MemoryObserver> memory_observer_; + scoped_ptr<base::debug::TraceMemoryController> trace_memory_controller_; DISALLOW_COPY_AND_ASSIGN(BrowserMainLoop); }; diff --git a/content/child/child_thread.cc b/content/child/child_thread.cc index 05650ad..16bfae2 100644 --- a/content/child/child_thread.cc +++ b/content/child/child_thread.cc @@ -35,6 +35,10 @@ #include "content/common/handle_enumerator_win.h" #endif +#if defined(TCMALLOC_TRACE_MEMORY_SUPPORTED) +#include "third_party/tcmalloc/chromium/src/gperftools/heap-profiler.h" +#endif + using tracked_objects::ThreadData; namespace content { @@ -173,6 +177,14 @@ void ChildThread::Init() { #if defined(OS_ANDROID) g_child_thread = this; #endif + +#if defined(TCMALLOC_TRACE_MEMORY_SUPPORTED) + trace_memory_controller_.reset(new base::debug::TraceMemoryController( + message_loop_->message_loop_proxy(), + ::HeapProfilerWithPseudoStackStart, + ::HeapProfilerStop, + ::GetHeapProfile)); +#endif } ChildThread::~ChildThread() { diff --git a/content/child/child_thread.h b/content/child/child_thread.h index eb060ee..3b37e87 100644 --- a/content/child/child_thread.h +++ b/content/child/child_thread.h @@ -17,16 +17,20 @@ namespace base { class MessageLoop; -} + +namespace debug { +class TraceMemoryController; +} // namespace debug +} // namespace base namespace IPC { class SyncChannel; class SyncMessageFilter; -} +} // namespace IPC namespace WebKit { class WebFrame; -} +} // namespace WebKit namespace content { class ChildHistogramMessageFilter; @@ -186,6 +190,10 @@ class CONTENT_EXPORT ChildThread : public IPC::Listener, public IPC::Sender { base::WeakPtrFactory<ChildThread> channel_connected_factory_; + // Observes the trace event system. When tracing is enabled, optionally + // starts profiling the tcmalloc heap. + scoped_ptr<base::debug::TraceMemoryController> trace_memory_controller_; + DISALLOW_COPY_AND_ASSIGN(ChildThread); }; diff --git a/third_party/tcmalloc/README.chromium b/third_party/tcmalloc/README.chromium index 9da19ca..0682e8d 100644 --- a/third_party/tcmalloc/README.chromium +++ b/third_party/tcmalloc/README.chromium @@ -89,3 +89,5 @@ Modifications: - Added support for android. - Use NULL instead of static_cast<uintptr_t>(0) in stack_trace_table.cc, for -std=c++11 compatibility. +- Added support for pseudo-stack heap profiling via a callback to retrieve a + simulated stack from the embedding application. diff --git a/third_party/tcmalloc/chromium/src/gperftools/heap-profiler.h b/third_party/tcmalloc/chromium/src/gperftools/heap-profiler.h index 8e3ee96..c2f1699 100644 --- a/third_party/tcmalloc/chromium/src/gperftools/heap-profiler.h +++ b/third_party/tcmalloc/chromium/src/gperftools/heap-profiler.h @@ -68,9 +68,24 @@ extern "C" { /* Start profiling and arrange to write profile data to file names * of the form: "prefix.0000", "prefix.0001", ... + * + * If |prefix| is NULL then dumps will not be written to disk. Applications + * can use GetHeapProfile() to get profile data, but HeapProfilerDump() will do + * nothing. */ PERFTOOLS_DLL_DECL void HeapProfilerStart(const char* prefix); +/* Start profiling with a callback function that returns application-generated + * stacks. Profiles are not written to disk, but may be obtained via + * GetHeapProfile(). The callback: + * 1. May optionally skip the first |skip_count| items on the stack. + * 2. Must provide a |stack| buffer of at least size 32 * sizeof(void*). + * 3. Must return the number of items copied or zero. + */ +typedef int (*StackGeneratorFunction)(int skip_count, void** stack); +PERFTOOLS_DLL_DECL void HeapProfilerWithPseudoStackStart( + StackGeneratorFunction callback); + /* Returns non-zero if we are currently profiling the heap. (Returns * an int rather than a bool so it's usable from C.) This is true * between calls to HeapProfilerStart() and HeapProfilerStop(), and diff --git a/third_party/tcmalloc/chromium/src/heap-profiler.cc b/third_party/tcmalloc/chromium/src/heap-profiler.cc index eb993a5..8d7bc42 100644 --- a/third_party/tcmalloc/chromium/src/heap-profiler.cc +++ b/third_party/tcmalloc/chromium/src/heap-profiler.cc @@ -222,6 +222,11 @@ static int64 last_dump_time = 0; // The time of the last dump static HeapProfileTable* heap_profile = NULL; // the heap profile table static DeepHeapProfile* deep_profile = NULL; // deep memory profiler +// Callback to generate a stack trace for an allocation. May be overriden +// by an application to provide its own pseudo-stacks. +static StackGeneratorFunction stack_generator_function = + HeapProfileTable::GetCallerStackTrace; + //---------------------------------------------------------------------- // Profile generation //---------------------------------------------------------------------- @@ -374,7 +379,7 @@ static void MaybeDumpProfileLocked() { static void RecordAlloc(const void* ptr, size_t bytes, int skip_count) { // Take the stack trace outside the critical section. void* stack[HeapProfileTable::kMaxStackDepth]; - int depth = HeapProfileTable::GetCallerStackTrace(skip_count + 1, stack); + int depth = stack_generator_function(skip_count + 1, stack); SpinLockHolder l(&heap_lock); if (is_on) { heap_profile->RecordAlloc(ptr, bytes, depth, stack); @@ -542,7 +547,9 @@ extern "C" void HeapProfilerStart(const char* prefix) { RAW_CHECK(MallocHook::AddDeleteHook(&DeleteHook), ""); } - // Copy filename prefix + // Copy filename prefix only if provided. + if (!prefix) + return; RAW_DCHECK(filename_prefix == NULL, ""); const int prefix_length = strlen(prefix); filename_prefix = reinterpret_cast<char*>(ProfilerMalloc(prefix_length + 1)); @@ -550,6 +557,16 @@ extern "C" void HeapProfilerStart(const char* prefix) { filename_prefix[prefix_length] = '\0'; } +extern "C" void HeapProfilerWithPseudoStackStart( + StackGeneratorFunction callback) { + { + // Ensure the callback is set before allocations can be recorded. + SpinLockHolder l(&heap_lock); + stack_generator_function = callback; + } + HeapProfilerStart(NULL); +} + extern "C" void IterateAllocatedObjects(AddressVisitor visitor, void* data) { SpinLockHolder l(&heap_lock); |