1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
|
// Copyright 2015 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/trace_event/heap_profiler_allocation_context_tracker.h"
#include <algorithm>
#include <iterator>
#include "base/atomicops.h"
#include "base/debug/leak_annotations.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread_local_storage.h"
#include "base/trace_event/heap_profiler_allocation_context.h"
#if defined(OS_LINUX) || defined(OS_ANDROID)
#include <sys/prctl.h>
#endif
namespace base {
namespace trace_event {
subtle::Atomic32 AllocationContextTracker::capture_mode_ =
static_cast<int32_t>(AllocationContextTracker::CaptureMode::DISABLED);
namespace {
const size_t kMaxStackDepth = 128u;
const size_t kMaxTaskDepth = 16u;
AllocationContextTracker* const kInitializingSentinel =
reinterpret_cast<AllocationContextTracker*>(-1);
ThreadLocalStorage::StaticSlot g_tls_alloc_ctx_tracker = TLS_INITIALIZER;
// This function is added to the TLS slot to clean up the instance when the
// thread exits.
void DestructAllocationContextTracker(void* alloc_ctx_tracker) {
delete static_cast<AllocationContextTracker*>(alloc_ctx_tracker);
}
// Cannot call ThreadIdNameManager::GetName because it holds a lock and causes
// deadlock when lock is already held by ThreadIdNameManager before the current
// allocation. Gets the thread name from kernel if available or returns a string
// with id. This function intenionally leaks the allocated strings since they
// are used to tag allocations even after the thread dies.
const char* GetAndLeakThreadName() {
char name[16];
#if defined(OS_LINUX) || defined(OS_ANDROID)
// If the thread name is not set, try to get it from prctl. Thread name might
// not be set in cases where the thread started before heap profiling was
// enabled.
int err = prctl(PR_GET_NAME, name);
if (!err) {
return strdup(name);
}
#endif // defined(OS_LINUX) || defined(OS_ANDROID)
// Use tid if we don't have a thread name.
snprintf(name, sizeof(name), "%lu",
static_cast<unsigned long>(PlatformThread::CurrentId()));
return strdup(name);
}
} // namespace
// static
AllocationContextTracker*
AllocationContextTracker::GetInstanceForCurrentThread() {
AllocationContextTracker* tracker =
static_cast<AllocationContextTracker*>(g_tls_alloc_ctx_tracker.Get());
if (tracker == kInitializingSentinel)
return nullptr; // Re-entrancy case.
if (!tracker) {
g_tls_alloc_ctx_tracker.Set(kInitializingSentinel);
tracker = new AllocationContextTracker();
g_tls_alloc_ctx_tracker.Set(tracker);
}
return tracker;
}
AllocationContextTracker::AllocationContextTracker()
: thread_name_(nullptr), ignore_scope_depth_(0) {
pseudo_stack_.reserve(kMaxStackDepth);
task_contexts_.reserve(kMaxTaskDepth);
}
AllocationContextTracker::~AllocationContextTracker() {}
// static
void AllocationContextTracker::SetCurrentThreadName(const char* name) {
if (name && capture_mode() != CaptureMode::DISABLED) {
GetInstanceForCurrentThread()->thread_name_ = name;
}
}
// static
void AllocationContextTracker::SetCaptureMode(CaptureMode mode) {
// When enabling capturing, also initialize the TLS slot. This does not create
// a TLS instance yet.
if (mode != CaptureMode::DISABLED && !g_tls_alloc_ctx_tracker.initialized())
g_tls_alloc_ctx_tracker.Initialize(DestructAllocationContextTracker);
// Release ordering ensures that when a thread observes |capture_mode_| to
// be true through an acquire load, the TLS slot has been initialized.
subtle::Release_Store(&capture_mode_, static_cast<int32_t>(mode));
}
void AllocationContextTracker::PushPseudoStackFrame(
AllocationContextTracker::PseudoStackFrame stack_frame) {
// Impose a limit on the height to verify that every push is popped, because
// in practice the pseudo stack never grows higher than ~20 frames.
if (pseudo_stack_.size() < kMaxStackDepth)
pseudo_stack_.push_back(stack_frame);
else
NOTREACHED();
}
void AllocationContextTracker::PopPseudoStackFrame(
AllocationContextTracker::PseudoStackFrame stack_frame) {
// Guard for stack underflow. If tracing was started with a TRACE_EVENT in
// scope, the frame was never pushed, so it is possible that pop is called
// on an empty stack.
if (pseudo_stack_.empty())
return;
// Assert that pushes and pops are nested correctly. This DCHECK can be
// hit if some TRACE_EVENT macro is unbalanced (a TRACE_EVENT_END* call
// without a corresponding TRACE_EVENT_BEGIN).
DCHECK(stack_frame == pseudo_stack_.back())
<< "Encountered an unmatched TRACE_EVENT_END: "
<< stack_frame.trace_event_name
<< " vs event in stack: " << pseudo_stack_.back().trace_event_name;
pseudo_stack_.pop_back();
}
void AllocationContextTracker::PushCurrentTaskContext(const char* context) {
DCHECK(context);
if (task_contexts_.size() < kMaxTaskDepth)
task_contexts_.push_back(context);
else
NOTREACHED();
}
void AllocationContextTracker::PopCurrentTaskContext(const char* context) {
// Guard for stack underflow. If tracing was started with a TRACE_EVENT in
// scope, the context was never pushed, so it is possible that pop is called
// on an empty stack.
if (task_contexts_.empty())
return;
DCHECK_EQ(context, task_contexts_.back())
<< "Encountered an unmatched context end";
task_contexts_.pop_back();
}
// static
bool AllocationContextTracker::GetContextSnapshot(AllocationContext* ctx) {
if (ignore_scope_depth_)
return false;
CaptureMode mode = static_cast<CaptureMode>(
subtle::NoBarrier_Load(&capture_mode_));
auto* backtrace = std::begin(ctx->backtrace.frames);
auto* backtrace_end = std::end(ctx->backtrace.frames);
if (!thread_name_) {
// Ignore the string allocation made by GetAndLeakThreadName to avoid
// reentrancy.
ignore_scope_depth_++;
thread_name_ = GetAndLeakThreadName();
ANNOTATE_LEAKING_OBJECT_PTR(thread_name_);
DCHECK(thread_name_);
ignore_scope_depth_--;
}
// Add the thread name as the first entry in pseudo stack.
if (thread_name_) {
*backtrace++ = StackFrame::FromThreadName(thread_name_);
}
switch (mode) {
case CaptureMode::DISABLED:
{
break;
}
case CaptureMode::PSEUDO_STACK:
{
for (const PseudoStackFrame& stack_frame : pseudo_stack_) {
if (backtrace == backtrace_end) {
break;
}
*backtrace++ =
StackFrame::FromTraceEventName(stack_frame.trace_event_name);
}
break;
}
case CaptureMode::NATIVE_STACK:
{
// Backtrace contract requires us to return bottom frames, i.e.
// from main() and up. Stack unwinding produces top frames, i.e.
// from this point and up until main(). We request many frames to
// make sure we reach main(), and then copy bottom portion of them.
const void* frames[128];
static_assert(arraysize(frames) >= Backtrace::kMaxFrameCount,
"not requesting enough frames to fill Backtrace");
#if HAVE_TRACE_STACK_FRAME_POINTERS && !defined(OS_NACL)
size_t frame_count = debug::TraceStackFramePointers(
frames,
arraysize(frames),
1 /* exclude this function from the trace */ );
#else
size_t frame_count = 0;
NOTREACHED();
#endif
// Copy frames backwards
size_t backtrace_capacity = backtrace_end - backtrace;
size_t top_frame_index = (backtrace_capacity >= frame_count) ?
0 :
frame_count - backtrace_capacity;
for (size_t i = frame_count; i > top_frame_index;) {
const void* frame = frames[--i];
*backtrace++ = StackFrame::FromProgramCounter(frame);
}
break;
}
}
ctx->backtrace.frame_count = backtrace - std::begin(ctx->backtrace.frames);
// TODO(ssid): Fix crbug.com/594803 to add file name as 3rd dimension
// (component name) in the heap profiler and not piggy back on the type name.
if (!task_contexts_.empty()) {
ctx->type_name = task_contexts_.back();
} else if (!pseudo_stack_.empty()) {
// If task context was unavailable, then the category names are taken from
// trace events.
ctx->type_name = pseudo_stack_.back().trace_event_category;
} else {
ctx->type_name = nullptr;
}
return true;
}
} // namespace trace_event
} // namespace base
|