// 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_register.h"
#include <algorithm>
#include <limits>
#include "base/trace_event/trace_event_memory_overhead.h"
namespace base {
namespace trace_event {
AllocationRegister::ConstIterator::ConstIterator(
const AllocationRegister& alloc_register,
AllocationIndex index)
: register_(alloc_register), index_(index) {}
void AllocationRegister::ConstIterator::operator++() {
index_ = register_.allocations_.Next(index_ + 1);
}
bool AllocationRegister::ConstIterator::operator!=(
const ConstIterator& other) const {
return index_ != other.index_;
}
AllocationRegister::Allocation AllocationRegister::ConstIterator::operator*()
const {
return register_.GetAllocation(index_);
}
size_t AllocationRegister::BacktraceHasher::operator()(
const Backtrace& backtrace) const {
const size_t kSampleLength = 10;
uintptr_t total_value = 0;
size_t head_end = std::min(backtrace.frame_count, kSampleLength);
for (size_t i = 0; i != head_end; ++i) {
total_value += reinterpret_cast<uintptr_t>(backtrace.frames[i].value);
}
size_t tail_start = backtrace.frame_count -
std::min(backtrace.frame_count - head_end, kSampleLength);
for (size_t i = tail_start; i != backtrace.frame_count; ++i) {
total_value += reinterpret_cast<uintptr_t>(backtrace.frames[i].value);
}
total_value += backtrace.frame_count;
// These magic constants give best results in terms of average collisions
// per backtrace. They were found by replaying real backtraces from Linux
// and Android against different hash functions.
return (total_value * 131101) >> 14;
}
size_t AllocationRegister::AddressHasher::operator()(
const void* address) const {
// The multiplicative hashing scheme from [Knuth 1998]. The value of |a| has
// been chosen carefully based on measurements with real-word data (addresses
// recorded from a Chrome trace run). It is the first prime after 2^17. For
// |shift|, 15 yield good results for both 2^18 and 2^19 bucket sizes.
// Microbenchmarks show that this simple scheme outperforms fancy hashes like
// Murmur3 by 20 to 40 percent.
const uintptr_t key = reinterpret_cast<uintptr_t>(address);
const uintptr_t a = 131101;
const uintptr_t shift = 15;
const uintptr_t h = (key * a) >> shift;
return h;
}
AllocationRegister::AllocationRegister()
: AllocationRegister(kAllocationCapacity, kBacktraceCapacity) {}
AllocationRegister::AllocationRegister(size_t allocation_capacity,
size_t backtrace_capacity)
: allocations_(allocation_capacity), backtraces_(backtrace_capacity) {
Backtrace sentinel = {};
sentinel.frames[0] = StackFrame::FromThreadName("[out of heap profiler mem]");
sentinel.frame_count = 1;
// Rationale for max / 2: in theory we could just start the sentinel with a
// refcount == 0. However, using max / 2 allows short circuiting of the
// conditional in RemoveBacktrace() keeping the sentinel logic out of the fast
// path. From a functional viewpoint, the sentinel is safe even if we wrap
// over refcount because .
BacktraceMap::KVPair::second_type sentinel_refcount =
std::numeric_limits<BacktraceMap::KVPair::second_type>::max() / 2;
auto index_and_flag = backtraces_.Insert(sentinel, sentinel_refcount);
DCHECK(index_and_flag.second);
DCHECK_EQ(index_and_flag.first, kOutOfStorageBacktraceIndex);
}
AllocationRegister::~AllocationRegister() {}
bool AllocationRegister::Insert(const void* address,
size_t size,
const AllocationContext& context) {
DCHECK(address != nullptr);
if (size == 0) {
return false;
}
AllocationInfo info = {size, context.type_name,
InsertBacktrace(context.backtrace)};
// Try to insert the allocation.
auto index_and_flag = allocations_.Insert(address, info);
if (!index_and_flag.second &&
index_and_flag.first != AllocationMap::kInvalidKVIndex) {
// |address| is already there - overwrite the allocation info.
auto& old_info = allocations_.Get(index_and_flag.first).second;
RemoveBacktrace(old_info.backtrace_index);
old_info = info;
return true;
}
return index_and_flag.second;
}
void AllocationRegister::Remove(const void* address) {
auto index = allocations_.Find(address);
if (index == AllocationMap::kInvalidKVIndex) {
return;
}
const AllocationInfo& info = allocations_.Get(index).second;
RemoveBacktrace(info.backtrace_index);
allocations_.Remove(index);
}
bool AllocationRegister::Get(const void* address,
Allocation* out_allocation) const {
auto index = allocations_.Find(address);
if (index == AllocationMap::kInvalidKVIndex) {
return false;
}
if (out_allocation) {
*out_allocation = GetAllocation(index);
}
return true;
}
AllocationRegister::ConstIterator AllocationRegister::begin() const {
return ConstIterator(*this, allocations_.Next(0));
}
AllocationRegister::ConstIterator AllocationRegister::end() const {
return ConstIterator(*this, AllocationMap::kInvalidKVIndex);
}
void AllocationRegister::EstimateTraceMemoryOverhead(
TraceEventMemoryOverhead* overhead) const {
size_t allocated = sizeof(AllocationRegister);
size_t resident = sizeof(AllocationRegister) +
allocations_.EstimateUsedMemory() +
backtraces_.EstimateUsedMemory();
overhead->Add("AllocationRegister", allocated, resident);
}
AllocationRegister::BacktraceMap::KVIndex AllocationRegister::InsertBacktrace(
const Backtrace& backtrace) {
auto index = backtraces_.Insert(backtrace, 0).first;
if (index == BacktraceMap::kInvalidKVIndex)
return kOutOfStorageBacktraceIndex;
auto& backtrace_and_count = backtraces_.Get(index);
backtrace_and_count.second++;
return index;
}
void AllocationRegister::RemoveBacktrace(BacktraceMap::KVIndex index) {
auto& backtrace_and_count = backtraces_.Get(index);
if (--backtrace_and_count.second == 0 &&
index != kOutOfStorageBacktraceIndex) {
// Backtrace is not referenced anymore - remove it.
backtraces_.Remove(index);
}
}
AllocationRegister::Allocation AllocationRegister::GetAllocation(
AllocationMap::KVIndex index) const {
const auto& address_and_info = allocations_.Get(index);
const auto& backtrace_and_count =
backtraces_.Get(address_and_info.second.backtrace_index);
return {address_and_info.first, address_and_info.second.size,
AllocationContext(backtrace_and_count.first,
address_and_info.second.type_name)};
}
} // namespace trace_event
} // namespace base