blob: ed8d759ad1191712465ada9f89b2cb764c8901b1 [file] [log] [blame]
// 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/memory_profiler_allocation_context.h"
#include <algorithm>
#include <cstring>
#include "base/hash.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread_local_storage.h"
#include "base/trace_event/trace_event_argument.h"
#include "base/trace_event/trace_event_memory_overhead.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);
}
} // namespace
AllocationStack::AllocationStack() {}
AllocationStack::~AllocationStack() {}
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;
}
StackFrameDeduplicator::FrameNode::FrameNode(StackFrame frame,
int parent_frame_index)
: frame(frame), parent_frame_index(parent_frame_index) {}
StackFrameDeduplicator::FrameNode::~FrameNode() {}
StackFrameDeduplicator::StackFrameDeduplicator() {}
StackFrameDeduplicator::~StackFrameDeduplicator() {}
int StackFrameDeduplicator::Insert(const Backtrace& bt) {
int frame_index = -1;
std::map<StackFrame, int>* nodes = &roots_;
for (size_t i = 0; i < arraysize(bt.frames); i++) {
if (!bt.frames[i])
break;
auto node = nodes->find(bt.frames[i]);
if (node == nodes->end()) {
// There is no tree node for this frame yet, create it. The parent node
// is the node associated with the previous frame.
FrameNode frame_node(bt.frames[i], frame_index);
// The new frame node will be appended, so its index is the current size
// of the vector.
frame_index = static_cast<int>(frames_.size());
// Add the node to the trie so it will be found next time.
nodes->insert(std::make_pair(bt.frames[i], frame_index));
// Append the node after modifying |children|, because the vector might
// need to resize, and this invalidates the |children| pointer.
frames_.push_back(frame_node);
} else {
// A tree node for this frame exists. Look for the next one.
frame_index = node->second;
}
nodes = &frames_[frame_index].children;
}
return frame_index;
}
void StackFrameDeduplicator::AppendAsTraceFormat(std::string* out) const {
out->append("{"); // Begin the |stackFrames| dictionary.
int i = 0;
auto frame_node = begin();
auto it_end = end();
std::string stringify_buffer;
while (frame_node != it_end) {
// The |stackFrames| format is a dictionary, not an array, so the
// keys are stringified indices. Write the index manually, then use
// |TracedValue| to format the object. This is to avoid building the
// entire dictionary as a |TracedValue| in memory.
SStringPrintf(&stringify_buffer, "\"%d\":", i);
out->append(stringify_buffer);
scoped_refptr<TracedValue> frame_node_value = new TracedValue;
frame_node_value->SetString("name", frame_node->frame);
if (frame_node->parent_frame_index >= 0) {
SStringPrintf(&stringify_buffer, "%d", frame_node->parent_frame_index);
frame_node_value->SetString("parent", stringify_buffer);
}
frame_node_value->AppendAsTraceFormat(out);
i++;
frame_node++;
if (frame_node != it_end)
out->append(",");
}
out->append("}"); // End the |stackFrames| dictionary.
}
void StackFrameDeduplicator::EstimateTraceMemoryOverhead(
TraceEventMemoryOverhead* overhead) {
// The sizes here are only estimates; they fail to take into account the
// overhead of the tree nodes for the map, but as an estimate this should be
// fine.
size_t maps_size = roots_.size() * sizeof(std::pair<StackFrame, int>);
size_t frames_allocated = frames_.capacity() * sizeof(FrameNode);
size_t frames_resident = frames_.size() * sizeof(FrameNode);
for (const FrameNode& node : frames_)
maps_size += node.children.size() * sizeof(std::pair<StackFrame, int>);
overhead->Add("StackFrameDeduplicator",
sizeof(StackFrameDeduplicator) + maps_size + frames_allocated,
sizeof(StackFrameDeduplicator) + maps_size + frames_resident);
}
bool operator==(const AllocationContext& lhs, const AllocationContext& rhs) {
return (lhs.backtrace == rhs.backtrace) && (lhs.type_id == rhs.type_id);
}
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;
}
AllocationContextTracker::AllocationContextTracker() {}
AllocationContextTracker::~AllocationContextTracker() {}
// 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();
tracker->pseudo_stack_.push(frame);
}
// static
void AllocationContextTracker::PopPseudoStackFrame(StackFrame frame) {
auto tracker = AllocationContextTracker::GetThreadLocalTracker();
tracker->pseudo_stack_.pop(frame);
}
// 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;
}
// static
AllocationContext AllocationContextTracker::GetContextSnapshot() {
AllocationContextTracker* tracker = GetThreadLocalTracker();
AllocationContext ctx;
// Fill the backtrace.
{
auto src = tracker->pseudo_stack_.bottom();
auto dst = ctx.backtrace.frames;
auto src_end = tracker->pseudo_stack_.top();
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
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