Skip to content

Commit

Permalink
[Tracing] Clean up heap profiler files
Browse files Browse the repository at this point in the history
Over time, base/trace_event/memory_profiler_allocation_context.h had
become a god header that contained nearly all heap profiler classes.
This CL splits the file (along with its tests) into multiple smaller
files. Furthermore, there are small comment and readability fixes. No
behavior is changed by this CL.

 * Change prefix of heap profiler files from |memory_profiler_*| to
   |heap_profiler_*|.

 * Move |AllocationContextTracker| and its tests into its own file.

 * Move |StackFrameDeduplicator| and its tests into its own file.

 * Remove |AllocationStack|, which was just a very thin wrapper around
   |std::vector|. Inline its logic into |AllocationContextTracker|
   instead.

 * Rename the |SortDescending| function in the heap dump writer source
   file to |SortBySizeDescending|.

 * Fix variable names in comments in |AllocationRegister|.

 * Fix and clarify comments in |AllocationContextTracker| and
   |StackFrameDeduplicator|.

 * Include what you use fixes.

BUG=524631

Review URL: https://codereview.chromium.org/1431103004

Cr-Commit-Position: refs/heads/master@{#359813}
  • Loading branch information
ruuda authored and Commit bot committed Nov 16, 2015
1 parent 89308c9 commit 6b25c30
Show file tree
Hide file tree
Showing 23 changed files with 712 additions and 631 deletions.
25 changes: 15 additions & 10 deletions base/trace_event/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@
source_set("trace_event") {
sources = [
"common/trace_event_common.h",
"heap_profiler_allocation_context.cc",
"heap_profiler_allocation_context.h",
"heap_profiler_allocation_context_tracker.cc",
"heap_profiler_allocation_context_tracker.h",
"heap_profiler_allocation_register.cc",
"heap_profiler_allocation_register.h",
"heap_profiler_allocation_register_posix.cc",
"heap_profiler_allocation_register_win.cc",
"heap_profiler_heap_dump_writer.cc",
"heap_profiler_heap_dump_writer.h",
"heap_profiler_stack_frame_deduplicator.cc",
"heap_profiler_stack_frame_deduplicator.h",
"java_heap_dump_provider_android.cc",
"java_heap_dump_provider_android.h",
"memory_allocator_dump.cc",
Expand All @@ -18,14 +30,6 @@ source_set("trace_event") {
"memory_dump_request_args.h",
"memory_dump_session_state.cc",
"memory_dump_session_state.h",
"memory_profiler_allocation_context.cc",
"memory_profiler_allocation_context.h",
"memory_profiler_allocation_register.cc",
"memory_profiler_allocation_register.h",
"memory_profiler_allocation_register_posix.cc",
"memory_profiler_allocation_register_win.cc",
"memory_profiler_heap_dump_writer.cc",
"memory_profiler_heap_dump_writer.h",
"process_memory_dump.cc",
"process_memory_dump.h",
"process_memory_maps.cc",
Expand Down Expand Up @@ -108,11 +112,12 @@ source_set("trace_event") {
source_set("trace_event_unittests") {
testonly = true
sources = [
"heap_profiler_allocation_context_tracker_unittest.cc",
"heap_profiler_allocation_register_unittest.cc",
"heap_profiler_stack_frame_deduplicator_unittest.cc",
"java_heap_dump_provider_android_unittest.cc",
"memory_allocator_dump_unittest.cc",
"memory_dump_manager_unittest.cc",
"memory_profiler_allocation_context_unittest.cc",
"memory_profiler_allocation_register_unittest.cc",
"process_memory_dump_unittest.cc",
"process_memory_totals_dump_provider_unittest.cc",
"trace_config_memory_test_util.h",
Expand Down
46 changes: 46 additions & 0 deletions base/trace_event/heap_profiler_allocation_context.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// 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.h"

#include <cstring>

#include "base/hash.h"

namespace base {
namespace trace_event {

bool operator==(const Backtrace& lhs, const Backtrace& rhs) {
// Pointer equality of the stack frames is assumed, so instead of doing a deep
// string comparison on all of the frames, a |memcmp| suffices.
return std::memcmp(lhs.frames, rhs.frames, sizeof(lhs.frames)) == 0;
}

bool operator==(const AllocationContext& lhs, const AllocationContext& rhs) {
return (lhs.backtrace == rhs.backtrace) && (lhs.type_id == rhs.type_id);
}

} // namespace trace_event
} // namespace base

namespace BASE_HASH_NAMESPACE {
using base::trace_event::AllocationContext;
using base::trace_event::Backtrace;

size_t hash<Backtrace>::operator()(const Backtrace& backtrace) const {
return base::SuperFastHash(reinterpret_cast<const char*>(backtrace.frames),
sizeof(backtrace.frames));
}

size_t hash<AllocationContext>::operator()(const AllocationContext& ctx) const {
size_t ctx_hash = hash<Backtrace>()(ctx.backtrace);

// Multiply one side to break the commutativity of +. Multiplication with a
// number coprime to |numeric_limits<size_t>::max() + 1| is bijective so
// randomness is preserved. The type ID is assumed to be distributed randomly
// already so there is no need to hash it.
return (ctx_hash * 3) + static_cast<size_t>(ctx.type_id);
}

} // BASE_HASH_NAMESPACE
87 changes: 87 additions & 0 deletions base/trace_event/heap_profiler_allocation_context.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// 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.

#ifndef BASE_TRACE_EVENT_HEAP_PROFILER_ALLOCATION_CONTEXT_H_
#define BASE_TRACE_EVENT_HEAP_PROFILER_ALLOCATION_CONTEXT_H_

#include <stdint.h>

#include "base/base_export.h"
#include "base/containers/hash_tables.h"

namespace base {
namespace trace_event {

// When heap profiling is enabled, tracing keeps track of the allocation
// context for each allocation intercepted. It is generated by the
// |AllocationContextTracker| which keeps stacks of context in TLS.
// The tracker is initialized lazily.

// The backtrace in the allocation context is a snapshot of the stack. For now,
// this is the pseudo stack where frames are created by trace event macros. In
// the future, we might add the option to use the native call stack. In that
// case, |Backtrace| and |AllocationContextTracker::GetContextSnapshot| might
// have different implementations that can be selected by a compile time flag.

// The number of stack frames stored in the backtrace is a trade off between
// memory used for tracing and accuracy. Measurements done on a prototype
// revealed that:
//
// - In 60 percent of the cases, stack depth <= 7.
// - In 87 percent of the cases, stack depth <= 9.
// - In 95 percent of the cases, stack depth <= 11.
//
// See the design doc (https://goo.gl/4s7v7b) for more details.

using StackFrame = const char*;

struct BASE_EXPORT Backtrace {
// Unused backtrace frames are filled with nullptr frames. If the stack is
// higher than what can be stored here, the bottom frames are stored. Based
// on the data above, a depth of 12 captures the full stack in the vast
// majority of the cases.
StackFrame frames[12];
};

bool BASE_EXPORT operator==(const Backtrace& lhs, const Backtrace& rhs);

// The |AllocationContext| is context metadata that is kept for every allocation
// when heap profiling is enabled. To simplify memory management for book-
// keeping, this struct has a fixed size. All |const char*|s here must have
// static lifetime.
// TODO(ruuda): Make the default constructor private to avoid accidentally
// constructing an instance and forgetting to initialize it. Only
// |AllocationContextTracker| should be able to construct. (And tests.)
struct BASE_EXPORT AllocationContext {
// A type ID is a number that is unique for every C++ type. A type ID is
// stored instead of the type name to avoid inflating the binary with type
// name strings. There is an out of band lookup table mapping IDs to the type
// names. A value of 0 means that the type is not known.
using TypeId = uint16_t;

Backtrace backtrace;
TypeId type_id;
};

bool BASE_EXPORT operator==(const AllocationContext& lhs,
const AllocationContext& rhs);

} // namespace trace_event
} // namespace base

namespace BASE_HASH_NAMESPACE {

template <>
struct hash<base::trace_event::Backtrace> {
size_t operator()(const base::trace_event::Backtrace& backtrace) const;
};

template <>
struct hash<base::trace_event::AllocationContext> {
size_t operator()(const base::trace_event::AllocationContext& context) const;
};

} // BASE_HASH_NAMESPACE

#endif // BASE_TRACE_EVENT_HEAP_PROFILER_ALLOCATION_CONTEXT_H_
121 changes: 121 additions & 0 deletions base/trace_event/heap_profiler_allocation_context_tracker.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// 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 "base/atomicops.h"
#include "base/threading/thread_local_storage.h"
#include "base/trace_event/heap_profiler_allocation_context.h"

namespace base {
namespace trace_event {

subtle::Atomic32 AllocationContextTracker::capture_enabled_ = 0;

namespace {

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);
}

// Returns a pointer past the end of the fixed-size array |array| of |T| of
// length |N|, identical to C++11 |std::end|.
template <typename T, int N>
T* End(T(&array)[N]) {
return array + N;
}

} // namespace

AllocationContextTracker::AllocationContextTracker() {}
AllocationContextTracker::~AllocationContextTracker() {}

// static
AllocationContextTracker* AllocationContextTracker::GetThreadLocalTracker() {
auto tracker =
static_cast<AllocationContextTracker*>(g_tls_alloc_ctx_tracker.Get());

if (!tracker) {
tracker = new AllocationContextTracker();
g_tls_alloc_ctx_tracker.Set(tracker);
}

return tracker;
}

// static
void AllocationContextTracker::SetCaptureEnabled(bool enabled) {
// When enabling capturing, also initialize the TLS slot. This does not create
// a TLS instance yet.
if (enabled && !g_tls_alloc_ctx_tracker.initialized())
g_tls_alloc_ctx_tracker.Initialize(DestructAllocationContextTracker);

// Release ordering ensures that when a thread observes |capture_enabled_| to
// be true through an acquire load, the TLS slot has been initialized.
subtle::Release_Store(&capture_enabled_, enabled);
}

// static
void AllocationContextTracker::PushPseudoStackFrame(StackFrame frame) {
auto tracker = AllocationContextTracker::GetThreadLocalTracker();

// 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.
DCHECK_LT(tracker->pseudo_stack_.size(), 128u);
tracker->pseudo_stack_.push_back(frame);
}

// static
void AllocationContextTracker::PopPseudoStackFrame(StackFrame frame) {
auto tracker = AllocationContextTracker::GetThreadLocalTracker();

// 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 (tracker->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_EQ(frame, tracker->pseudo_stack_.back())
<< "Encountered an unmatched TRACE_EVENT_END";

tracker->pseudo_stack_.pop_back();
}

// static
AllocationContext AllocationContextTracker::GetContextSnapshot() {
AllocationContextTracker* tracker = GetThreadLocalTracker();
AllocationContext ctx;

// Fill the backtrace.
{
auto src = tracker->pseudo_stack_.begin();
auto dst = ctx.backtrace.frames;
auto src_end = tracker->pseudo_stack_.end();
auto dst_end = End(ctx.backtrace.frames);

// Copy as much of the bottom of the pseudo stack into the backtrace as
// possible.
for (; src != src_end && dst != dst_end; src++, dst++)
*dst = *src;

// If there is room for more, fill the remaining slots with empty frames.
std::fill(dst, dst_end, nullptr);
}

ctx.type_id = 0;

return ctx;
}

} // namespace trace_event
} // namespace base
73 changes: 73 additions & 0 deletions base/trace_event/heap_profiler_allocation_context_tracker.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// 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.

#ifndef BASE_TRACE_EVENT_HEAP_PROFILER_ALLOCATION_CONTEXT_TRACKER_H_
#define BASE_TRACE_EVENT_HEAP_PROFILER_ALLOCATION_CONTEXT_TRACKER_H_

#include <vector>

#include "base/atomicops.h"
#include "base/base_export.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/trace_event/heap_profiler_allocation_context.h"

namespace base {
namespace trace_event {

// The allocation context tracker keeps track of thread-local context for heap
// profiling. It includes a pseudo stack of trace events. On every allocation
// the tracker provides a snapshot of its context in the form of an
// |AllocationContext| that is to be stored together with the allocation
// details.
class BASE_EXPORT AllocationContextTracker {
public:
// Globally enables capturing allocation context.
// TODO(ruuda): Should this be replaced by |EnableCapturing| in the future?
// Or at least have something that guards agains enable -> disable -> enable?
static void SetCaptureEnabled(bool enabled);

// Returns whether capturing allocation context is enabled globally.
inline static bool capture_enabled() {
// A little lag after heap profiling is enabled or disabled is fine, it is
// more important that the check is as cheap as possible when capturing is
// not enabled, so do not issue a memory barrier in the fast path.
if (subtle::NoBarrier_Load(&capture_enabled_) == 0)
return false;

// In the slow path, an acquire load is required to pair with the release
// store in |SetCaptureEnabled|. This is to ensure that the TLS slot for
// the thread-local allocation context tracker has been initialized if
// |capture_enabled| returns true.
return subtle::Acquire_Load(&capture_enabled_) != 0;
}

// Pushes a frame onto the thread-local pseudo stack.
static void PushPseudoStackFrame(StackFrame frame);

// Pops a frame from the thread-local pseudo stack.
static void PopPseudoStackFrame(StackFrame frame);

// Returns a snapshot of the current thread-local context.
static AllocationContext GetContextSnapshot();

~AllocationContextTracker();

private:
AllocationContextTracker();

static AllocationContextTracker* GetThreadLocalTracker();

static subtle::Atomic32 capture_enabled_;

// The pseudo stack where frames are |TRACE_EVENT| names.
std::vector<StackFrame> pseudo_stack_;

DISALLOW_COPY_AND_ASSIGN(AllocationContextTracker);
};

} // namespace trace_event
} // namespace base

#endif // BASE_TRACE_EVENT_HEAP_PROFILER_ALLOCATION_CONTEXT_TRACKER_H_
Loading

0 comments on commit 6b25c30

Please sign in to comment.