Index: base/debug/trace_event_memory.cc |
diff --git a/base/debug/trace_event_memory.cc b/base/debug/trace_event_memory.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..51f3d1017822d6f983a77b354e01ba0171727e1d |
--- /dev/null |
+++ b/base/debug/trace_event_memory.cc |
@@ -0,0 +1,411 @@ |
+// 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.h" |
+#include "base/strings/string_number_conversions.h" |
+#include "base/strings/string_util.h" |
+#include "base/threading/thread_local_storage.h" |
+ |
+// TODO(jamescook): Windows support for memory tracing. |
+#if !defined(NO_TCMALLOC) && !defined(OS_NACL) && \ |
+ (defined(OS_LINUX) || defined(OS_ANDROID)) |
+#define TRACE_MEMORY_SUPPORTED 1 |
+#endif |
+ |
+#if defined(TRACE_MEMORY_SUPPORTED) |
+#include "third_party/tcmalloc/chromium/src/gperftools/heap-profiler.h" |
+#endif |
+ |
+namespace base { |
+namespace debug { |
+ |
+namespace { |
+ |
+// Maximum number of nested TRACE_MEMORY scopes to record. Must be greater than |
+// or equal to HeapProfileTable::kMaxStackDepth. |
+const int 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. |
+ int index_; |
jar (doing other things)
2013/07/12 01:24:26
nit: better is size_t
James Cook
2013/07/12 17:40:27
Done.
|
+ 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); |
+} |
+ |
+void CleanupThreadLocalStorage() { |
+ if (tls_trace_memory_stack.initialized()) |
+ tls_trace_memory_stack.Free(); |
+} |
+ |
+// 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() { |
+ if (!tls_trace_memory_stack.initialized()) |
+ return NULL; |
+ 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 int 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])); |
+ return count; |
+} |
+ |
+// Caller owns the returned char* and must release it with free(). |
+char* TraceMemoryDumpAsString() { |
+#if defined(TRACE_MEMORY_SUPPORTED) |
+ DVLOG(1) << "TraceMemoryDumpAsString"; |
+ return ::GetHeapProfile(); |
+#else |
+ NOTREACHED(); |
+ return NULL; |
+#endif |
+} |
+ |
+// If memory tracing is enabled, dumps a memory profile to the tracing system. |
+void 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"; |
+ |
+ // 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; |
+ |
+ // MemoryDumpHolder takes ownership of this string. |
+ char* dump = TraceMemoryDumpAsString(); |
+ 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 TraceMemoryStart() { |
+#if defined(TRACE_MEMORY_SUPPORTED) |
+ DVLOG(1) << "Starting trace memory"; |
+ if (!InitThreadLocalStorage()) |
+ return; |
+ ::HeapProfilerWithPseudoStackStart(&GetPseudoStack); |
+#else |
+ NOTREACHED(); |
+#endif |
+} |
+ |
+void TraceMemoryStop() { |
+#if defined(TRACE_MEMORY_SUPPORTED) |
+ DVLOG(1) << "Stopping trace memory"; |
+ CleanupThreadLocalStorage(); |
+ ::HeapProfilerStop(); |
+#else |
+ NOTREACHED(); |
+#endif |
+} |
+ |
+} // namespace |
+ |
+////////////////////////////////////////////////////////////////////////////// |
+ |
+TraceMemoryController::TraceMemoryController( |
+ scoped_refptr<MessageLoopProxy> message_loop_proxy) |
+ : message_loop_proxy_(message_loop_proxy), |
+ weak_factory_(this) { |
+ // Force the "memory" category to show up in the trace viewer. |
+ TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("memory"), "init"); |
+ // Allow this to be instantiated on unsupported platforms, but don't run. |
+#if defined(TRACE_MEMORY_SUPPORTED) |
+ TraceLog::GetInstance()->AddEnabledStateObserver(this); |
+#endif |
+} |
+ |
+TraceMemoryController::~TraceMemoryController() { |
+#if defined(TRACE_MEMORY_SUPPORTED) |
+ if (dump_timer_.IsRunning()) |
+ StopProfiling(); |
+ TraceLog::GetInstance()->RemoveEnabledStateObserver(this); |
+#endif |
+} |
+ |
+ // base::debug::TraceLog::EnabledStateChangedObserver overrides: |
+void TraceMemoryController::OnTraceLogEnabled() { |
+ DVLOG(1) << "OnTraceLogEnabled"; |
+ message_loop_proxy_->PostTask( |
+ FROM_HERE, |
+ base::Bind(&TraceMemoryController::StartProfiling, |
+ weak_factory_.GetWeakPtr())); |
+} |
+ |
+void TraceMemoryController::OnTraceLogDisabled() { |
+ 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; |
+ TraceMemoryStart(); |
+ const int kDumpIntervalSeconds = 5; |
+ dump_timer_.Start(FROM_HERE, |
+ TimeDelta::FromSeconds(kDumpIntervalSeconds), |
+ base::Bind(&DumpMemoryProfile)); |
+} |
+ |
+void TraceMemoryController::StopProfiling() { |
+ dump_timer_.Stop(); |
+ TraceMemoryStop(); |
+} |
+ |
+bool TraceMemoryController::IsTimerRunningForTest() const { |
+ return dump_timer_.IsRunning(); |
+} |
+ |
+///////////////////////////////////////////////////////////////////////////// |
+ |
+ScopedTraceMemory::ScopedTraceMemory(const char* category) { |
+ // Get our thread's copy of the stack. |
+ TraceMemoryStack* trace_memory_stack = GetTraceMemoryStack(); |
+ // No TraceMemoryStack indicates that the trace system isn't running, so don't |
+ // record anything. |
+ if (!trace_memory_stack) |
+ return; |
+ const int index = trace_memory_stack->index_; |
jar (doing other things)
2013/07/12 01:24:26
nit: size_t
James Cook
2013/07/12 17:40:27
Done.
|
+ // 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() { |
+ // Get our thread's copy of the stack. |
+ TraceMemoryStack* trace_memory_stack = GetTraceMemoryStack(); |
+ // No TraceMemoryStack indicates that the trace system isn't running, so don't |
+ // record anything. |
+ if (!trace_memory_stack) |
+ return; |
+ // 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(); |
+} |
+ |
+// static |
+void ScopedTraceMemory::CleanupForTest() { |
+ CleanupThreadLocalStorage(); |
+} |
+ |
+// static |
+int ScopedTraceMemory::GetStackIndexForTest() { |
+ TraceMemoryStack* stack = GetTraceMemoryStack(); |
+ return 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); |
+ output->append(",\n"); |
+ |
+ // Handle the following stack trace lines. |
+ for (size_t i = 1; i < line_count; ++i) { |
+ const std::string& line = lines[i]; |
+ bool added_entry = AppendHeapProfileLineAsTraceFormat(line, output); |
+ if (added_entry) |
+ output->append(",\n"); |
+ } |
+ // Remove trailing ",\n". |
+ output->resize(output->size() - 2); |
jar (doing other things)
2013/07/12 01:24:26
I think this works... but the approach of backing-
James Cook
2013/07/12 17:40:27
Yes, that's cleaner, thanks. Used your suggestion
|
+ 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 |
+ 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 |
jar (doing other things)
2013/07/12 01:24:26
nit: Please add comments telling what these number
James Cook
2013/07/12 17:40:27
Done.
|
+ 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("{\"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 |