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 |