Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(5)

Side by Side Diff: base/trace_event/heap_profiler_allocation_context_tracker.cc

Issue 1839503002: [tracing] Add native allocation tracing mode. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Add type to StackFrame; format thread name Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698