summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjamescook@chromium.org <jamescook@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-07-24 18:38:29 +0000
committerjamescook@chromium.org <jamescook@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-07-24 18:38:29 +0000
commit111494e2d84d16764a5a6b1f70c096932ead454e (patch)
tree5a81c9ba1236b2801305e7865094e0749e1f2cd4
parentb544c1ae28772fc1392a815af620433d59293363 (diff)
downloadchromium_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.gyp1
-rw-r--r--base/base.gypi2
-rw-r--r--base/debug/trace_event.h12
-rw-r--r--base/debug/trace_event_impl.cc11
-rw-r--r--base/debug/trace_event_impl.h1
-rw-r--r--base/debug/trace_event_memory.cc414
-rw-r--r--base/debug/trace_event_memory.h152
-rw-r--r--base/debug/trace_event_memory_unittest.cc211
-rw-r--r--content/browser/browser_main_loop.cc14
-rw-r--r--content/browser/browser_main_loop.h10
-rw-r--r--content/child/child_thread.cc12
-rw-r--r--content/child/child_thread.h14
-rw-r--r--third_party/tcmalloc/README.chromium2
-rw-r--r--third_party/tcmalloc/chromium/src/gperftools/heap-profiler.h15
-rw-r--r--third_party/tcmalloc/chromium/src/heap-profiler.cc21
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);