Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "base/trace_event/heap_profiler_allocation_context_tracker.h" | 5 #include "base/trace_event/heap_profiler_allocation_context_tracker.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <iterator> | 8 #include <iterator> |
| 9 | 9 |
| 10 #include "base/allocator/features.h" | |
| 10 #include "base/atomicops.h" | 11 #include "base/atomicops.h" |
| 11 #include "base/threading/thread_local_storage.h" | 12 #include "base/threading/thread_local_storage.h" |
| 12 #include "base/trace_event/heap_profiler_allocation_context.h" | 13 #include "base/trace_event/heap_profiler_allocation_context.h" |
| 13 | 14 |
| 14 namespace base { | 15 namespace base { |
| 15 namespace trace_event { | 16 namespace trace_event { |
| 16 | 17 |
| 17 subtle::Atomic32 AllocationContextTracker::capture_enabled_ = 0; | 18 subtle::Atomic32 AllocationContextTracker::capture_mode_ = |
| 19 AllocationContextTracker::DONT_CAPTURE; | |
| 18 | 20 |
| 19 namespace { | 21 namespace { |
| 20 | 22 |
| 21 const size_t kMaxStackDepth = 128u; | 23 const size_t kMaxStackDepth = 128u; |
| 22 const size_t kMaxTaskDepth = 16u; | 24 const size_t kMaxTaskDepth = 16u; |
| 23 AllocationContextTracker* const kInitializingSentinel = | 25 AllocationContextTracker* const kInitializingSentinel = |
| 24 reinterpret_cast<AllocationContextTracker*>(-1); | 26 reinterpret_cast<AllocationContextTracker*>(-1); |
| 25 | 27 |
| 26 ThreadLocalStorage::StaticSlot g_tls_alloc_ctx_tracker = TLS_INITIALIZER; | 28 ThreadLocalStorage::StaticSlot g_tls_alloc_ctx_tracker = TLS_INITIALIZER; |
| 27 | 29 |
| 28 // This function is added to the TLS slot to clean up the instance when the | 30 // This function is added to the TLS slot to clean up the instance when the |
| 29 // thread exits. | 31 // thread exits. |
| 30 void DestructAllocationContextTracker(void* alloc_ctx_tracker) { | 32 void DestructAllocationContextTracker(void* alloc_ctx_tracker) { |
| 31 delete static_cast<AllocationContextTracker*>(alloc_ctx_tracker); | 33 delete static_cast<AllocationContextTracker*>(alloc_ctx_tracker); |
| 32 } | 34 } |
| 33 | 35 |
| 36 // Walks stack frames and collects a trace. Unlike base::debug::StackTrace | |
| 37 // (which uses unwinding) this function uses frame pointers and is fast. | |
| 38 // However, by default frame pointers are not enabled in optimized builds | |
| 39 // unless -fno-omit-frame-pointer flag is specified. | |
| 40 // Adapted from TCMalloc's stacktrace_x86-inl.h | |
| 41 size_t WalkStackFrames(const void** trace, | |
| 42 size_t max_depth, | |
| 43 size_t skip_count) { | |
| 44 uintptr_t sp = reinterpret_cast<uintptr_t>( | |
|
Primiano Tucci (use gerrit)
2016/04/07 15:51:56
Should this have a
#if !BUILDFLAG(ENABLE_PROFILIN
| |
| 45 __builtin_frame_address(0)); | |
| 46 | |
| 47 size_t n = 0; | |
| 48 while (sp && n < max_depth) { | |
| 49 if (reinterpret_cast<void**>(sp)[1] == nullptr) { | |
| 50 // In 64-bit code, we often see a frame that | |
| 51 // points to itself and has a return address of 0. | |
| 52 break; | |
| 53 } | |
| 54 | |
| 55 if (skip_count > 0) { | |
| 56 skip_count--; | |
| 57 } else { | |
| 58 trace[n] = reinterpret_cast<void**>(sp)[1]; | |
| 59 n++; | |
| 60 } | |
| 61 | |
| 62 { | |
| 63 uintptr_t next_sp = reinterpret_cast<uintptr_t*>(sp)[0]; | |
| 64 | |
| 65 // With the stack growing downwards, older stack frame must be | |
| 66 // at a greater address that the current one. | |
| 67 if (next_sp <= sp) break; | |
| 68 | |
| 69 // Assume stack frames larger than 100,000 bytes are bogus. | |
| 70 if (next_sp - sp > 100000) break; | |
| 71 | |
| 72 // Check alignment. | |
| 73 if (next_sp & (sizeof(void*) - 1)) break; | |
| 74 | |
| 75 #ifdef __i386__ | |
| 76 // On 64-bit machines, the stack pointer can be very close to | |
| 77 // 0xffffffff, so we explicitly check for a pointer into the | |
| 78 // last two pages in the address space | |
| 79 if (next_sp >= 0xffffe000) break; | |
| 80 #endif | |
| 81 | |
| 82 sp = next_sp; | |
| 83 } | |
| 84 } | |
| 85 | |
| 86 return n; | |
| 87 } | |
| 88 | |
| 34 } // namespace | 89 } // namespace |
| 35 | 90 |
| 36 // static | 91 // static |
| 37 AllocationContextTracker* | 92 AllocationContextTracker* |
| 38 AllocationContextTracker::GetInstanceForCurrentThread() { | 93 AllocationContextTracker::GetInstanceForCurrentThread() { |
| 39 AllocationContextTracker* tracker = | 94 AllocationContextTracker* tracker = |
| 40 static_cast<AllocationContextTracker*>(g_tls_alloc_ctx_tracker.Get()); | 95 static_cast<AllocationContextTracker*>(g_tls_alloc_ctx_tracker.Get()); |
| 41 if (tracker == kInitializingSentinel) | 96 if (tracker == kInitializingSentinel) |
| 42 return nullptr; // Re-entrancy case. | 97 return nullptr; // Re-entrancy case. |
| 43 | 98 |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 57 AllocationContextTracker::~AllocationContextTracker() {} | 112 AllocationContextTracker::~AllocationContextTracker() {} |
| 58 | 113 |
| 59 // static | 114 // static |
| 60 void AllocationContextTracker::SetCurrentThreadName(const char* name) { | 115 void AllocationContextTracker::SetCurrentThreadName(const char* name) { |
| 61 if (name && capture_enabled()) { | 116 if (name && capture_enabled()) { |
| 62 GetInstanceForCurrentThread()->thread_name_ = name; | 117 GetInstanceForCurrentThread()->thread_name_ = name; |
| 63 } | 118 } |
| 64 } | 119 } |
| 65 | 120 |
| 66 // static | 121 // static |
| 67 void AllocationContextTracker::SetCaptureEnabled(bool enabled) { | 122 void AllocationContextTracker::SetCaptureMode(CaptureMode mode) { |
| 68 // When enabling capturing, also initialize the TLS slot. This does not create | 123 // When enabling capturing, also initialize the TLS slot. This does not create |
| 69 // a TLS instance yet. | 124 // a TLS instance yet. |
| 70 if (enabled && !g_tls_alloc_ctx_tracker.initialized()) | 125 if (mode != DONT_CAPTURE && !g_tls_alloc_ctx_tracker.initialized()) |
| 71 g_tls_alloc_ctx_tracker.Initialize(DestructAllocationContextTracker); | 126 g_tls_alloc_ctx_tracker.Initialize(DestructAllocationContextTracker); |
| 72 | 127 |
| 73 // Release ordering ensures that when a thread observes |capture_enabled_| to | 128 // Release ordering ensures that when a thread observes |capture_mode_| to |
| 74 // be true through an acquire load, the TLS slot has been initialized. | 129 // be true through an acquire load, the TLS slot has been initialized. |
| 75 subtle::Release_Store(&capture_enabled_, enabled); | 130 subtle::Release_Store(&capture_mode_, mode); |
| 131 } | |
| 132 | |
| 133 void AllocationContextTracker::PushPseudoStackFrame(const char* frame_value) { | |
| 134 PushPseudoStackFrame(StackFrame::FromSymbol(frame_value)); | |
| 76 } | 135 } |
| 77 | 136 |
| 78 void AllocationContextTracker::PushPseudoStackFrame(StackFrame frame) { | 137 void AllocationContextTracker::PushPseudoStackFrame(StackFrame frame) { |
| 79 // Impose a limit on the height to verify that every push is popped, because | 138 // Impose a limit on the height to verify that every push is popped, because |
| 80 // in practice the pseudo stack never grows higher than ~20 frames. | 139 // in practice the pseudo stack never grows higher than ~20 frames. |
| 81 if (pseudo_stack_.size() < kMaxStackDepth) | 140 if (pseudo_stack_.size() < kMaxStackDepth) |
| 82 pseudo_stack_.push_back(frame); | 141 pseudo_stack_.push_back(frame); |
| 83 else | 142 else |
| 84 NOTREACHED(); | 143 NOTREACHED(); |
| 85 } | 144 } |
| 86 | 145 |
| 87 // static | 146 void AllocationContextTracker::PopPseudoStackFrame(const void* frame_value) { |
| 88 void AllocationContextTracker::PopPseudoStackFrame(StackFrame frame) { | |
| 89 // Guard for stack underflow. If tracing was started with a TRACE_EVENT in | 147 // Guard for stack underflow. If tracing was started with a TRACE_EVENT in |
| 90 // scope, the frame was never pushed, so it is possible that pop is called | 148 // scope, the frame was never pushed, so it is possible that pop is called |
| 91 // on an empty stack. | 149 // on an empty stack. |
| 92 if (pseudo_stack_.empty()) | 150 if (pseudo_stack_.empty()) |
| 93 return; | 151 return; |
| 94 | 152 |
| 95 // Assert that pushes and pops are nested correctly. This DCHECK can be | 153 // Assert that pushes and pops are nested correctly. This DCHECK can be |
| 96 // hit if some TRACE_EVENT macro is unbalanced (a TRACE_EVENT_END* call | 154 // hit if some TRACE_EVENT macro is unbalanced (a TRACE_EVENT_END* call |
| 97 // without a corresponding TRACE_EVENT_BEGIN). | 155 // without a corresponding TRACE_EVENT_BEGIN). |
| 98 DCHECK_EQ(frame, pseudo_stack_.back()) | 156 DCHECK_EQ(frame_value, pseudo_stack_.back().value) |
| 99 << "Encountered an unmatched TRACE_EVENT_END"; | 157 << "Encountered an unmatched TRACE_EVENT_END"; |
| 100 | 158 |
| 101 pseudo_stack_.pop_back(); | 159 pseudo_stack_.pop_back(); |
| 102 } | 160 } |
| 103 | 161 |
| 104 void AllocationContextTracker::PushCurrentTaskContext(const char* context) { | 162 void AllocationContextTracker::PushCurrentTaskContext(const char* context) { |
| 105 DCHECK(context); | 163 DCHECK(context); |
| 106 if (task_contexts_.size() < kMaxTaskDepth) | 164 if (task_contexts_.size() < kMaxTaskDepth) |
| 107 task_contexts_.push_back(context); | 165 task_contexts_.push_back(context); |
| 108 else | 166 else |
| 109 NOTREACHED(); | 167 NOTREACHED(); |
| 110 } | 168 } |
| 111 | 169 |
| 112 void AllocationContextTracker::PopCurrentTaskContext(const char* context) { | 170 void AllocationContextTracker::PopCurrentTaskContext(const char* context) { |
| 113 DCHECK_EQ(context, task_contexts_.back()) | 171 DCHECK_EQ(context, task_contexts_.back()) |
| 114 << "Encountered an unmatched context end"; | 172 << "Encountered an unmatched context end"; |
| 115 task_contexts_.pop_back(); | 173 task_contexts_.pop_back(); |
| 116 } | 174 } |
| 117 | 175 |
| 118 // static | |
| 119 AllocationContext AllocationContextTracker::GetContextSnapshot() { | 176 AllocationContext AllocationContextTracker::GetContextSnapshot() { |
| 120 AllocationContext ctx; | 177 AllocationContext ctx; |
| 121 | 178 |
| 122 // Fill the backtrace. | 179 CaptureMode mode = static_cast<CaptureMode>( |
| 123 { | 180 subtle::Acquire_Load(&capture_mode_)); |
|
Primiano Tucci (use gerrit)
2016/04/07 15:51:56
We are going to call this on each malloc. I'd keep
Dmitry Skiba
2016/04/12 18:22:11
Done.
| |
| 124 auto src = pseudo_stack_.begin(); | |
| 125 auto dst = std::begin(ctx.backtrace.frames); | |
| 126 auto src_end = pseudo_stack_.end(); | |
| 127 auto dst_end = std::end(ctx.backtrace.frames); | |
| 128 | 181 |
| 129 // Add the thread name as the first enrty in the backtrace. | 182 switch (mode) { |
| 130 if (thread_name_) { | 183 case DONT_CAPTURE: |
| 131 DCHECK(dst < dst_end); | 184 { |
| 132 *dst = thread_name_; | 185 NOTREACHED(); |
| 133 ++dst; | 186 return AllocationContext::Empty(); |
| 134 } | 187 } |
| 188 case CAPTURE_PSEUDO_STACK: | |
| 189 { | |
| 190 auto src = pseudo_stack_.begin(); | |
|
Primiano Tucci (use gerrit)
2016/04/07 15:51:56
I think there is some duplicated logic between thi
Dmitry Skiba
2016/04/12 18:22:11
Done.
| |
| 191 auto dst = std::begin(ctx.backtrace.frames); | |
| 192 auto src_end = pseudo_stack_.end(); | |
| 193 auto dst_end = std::end(ctx.backtrace.frames); | |
| 135 | 194 |
| 136 // Copy as much of the bottom of the pseudo stack into the backtrace as | 195 // Add the thread name as the first enrty in the backtrace. |
| 137 // possible. | 196 if (thread_name_) { |
| 138 for (; src != src_end && dst != dst_end; src++, dst++) | 197 DCHECK(dst < dst_end); |
| 139 *dst = *src; | 198 *dst = StackFrame::FromThreadName(thread_name_); |
| 199 ++dst; | |
| 200 } | |
| 140 | 201 |
| 141 // If there is room for more, fill the remaining slots with empty frames. | 202 // Copy as much of the bottom of the pseudo stack into the |
| 142 std::fill(dst, dst_end, nullptr); | 203 // backtrace as possible. |
| 204 for (; src != src_end && dst != dst_end; src++, dst++) | |
| 205 *dst = *src; | |
| 206 | |
| 207 // If there is room for more, fill the remaining slots with | |
| 208 // empty frames. | |
| 209 std::fill(dst, dst_end, StackFrame::Empty()); | |
| 210 | |
| 211 break; | |
| 212 } | |
| 213 case CAPTURE_NATIVE_STACK: | |
| 214 { | |
| 215 // WalkStackFrames() | |
| 216 // base::trace_event::AllocationContextTracker::GetContextSnapshot() | |
| 217 const size_t known_frame_count = 2; | |
| 218 | |
| 219 const size_t max_frame_count = arraysize(ctx.backtrace.frames); | |
| 220 const void* frames[max_frame_count]; | |
| 221 size_t frame_count = WalkStackFrames(frames, | |
| 222 max_frame_count, | |
| 223 known_frame_count); | |
| 224 | |
| 225 size_t frame_shift = 0; | |
| 226 if (thread_name_) { | |
| 227 ctx.backtrace.frames[frame_shift++] = | |
| 228 StackFrame::FromThreadName(thread_name_); | |
| 229 } | |
| 230 | |
| 231 if (frame_count + frame_shift > max_frame_count) { | |
| 232 frame_count = max_frame_count - frame_shift; | |
| 233 } | |
| 234 | |
| 235 // Copy frames backwards | |
| 236 for (size_t i = 0; i != frame_count; ++i) { | |
| 237 ctx.backtrace.frames[i + frame_shift] = | |
| 238 StackFrame::FromPC(frames[frame_count - 1 - i]); | |
| 239 } | |
| 240 | |
| 241 // Fill remainder with empty frames | |
| 242 std::fill(ctx.backtrace.frames + frame_shift + frame_count, | |
| 243 ctx.backtrace.frames + max_frame_count, | |
| 244 StackFrame::Empty()); | |
| 245 break; | |
| 246 } | |
| 143 } | 247 } |
| 144 | 248 |
| 145 // TODO(ssid): Fix crbug.com/594803 to add file name as 3rd dimension | 249 // TODO(ssid): Fix crbug.com/594803 to add file name as 3rd dimension |
| 146 // (component name) in the heap profiler and not piggy back on the type name. | 250 // (component name) in the heap profiler and not piggy back on the type name. |
| 147 ctx.type_name = task_contexts_.empty() ? nullptr : task_contexts_.back(); | 251 ctx.type_name = task_contexts_.empty() ? nullptr : task_contexts_.back(); |
| 148 | 252 |
| 149 return ctx; | 253 return ctx; |
| 150 } | 254 } |
| 151 | 255 |
| 152 } // namespace trace_event | 256 } // namespace trace_event |
| 153 } // namespace base | 257 } // namespace base |
| OLD | NEW |