blob: 1476d1a966049d472d9653706bf3f2fce0b072fd [file] [log] [blame]
// Copyright 2016 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/debug/activity_tracker.h"
#include <algorithm>
#include <limits>
#include <utility>
#include "base/atomic_sequence_num.h"
#include "base/debug/stack_trace.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/memory_mapped_file.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram_macros.h"
#include "base/pending_task.h"
#include "base/pickle.h"
#include "base/process/process.h"
#include "base/process/process_handle.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/platform_thread.h"
#include "build/build_config.h"
#include "starboard/common/string.h"
#include "starboard/memory.h"
namespace base {
namespace debug {
namespace {
// The minimum depth a stack should support.
const int kMinStackDepth = 2;
// The amount of memory set aside for holding arbitrary user data (key/value
// pairs) globally or associated with ActivityData entries.
const size_t kUserDataSize = 1 << 10; // 1 KiB
const size_t kProcessDataSize = 4 << 10; // 4 KiB
const size_t kMaxUserDataNameLength =
static_cast<size_t>(std::numeric_limits<uint8_t>::max());
// A constant used to indicate that module information is changing.
const uint32_t kModuleInformationChanging = 0x80000000;
// The key used to record process information.
const char kProcessPhaseDataKey[] = "process-phase";
// An atomically incrementing number, used to check for recreations of objects
// in the same memory space.
AtomicSequenceNumber g_next_id;
// Gets the next non-zero identifier. It is only unique within a process.
uint32_t GetNextDataId() {
uint32_t id;
while ((id = g_next_id.GetNext()) == 0)
;
return id;
}
// Gets the current process-id, either from the GlobalActivityTracker if it
// exists (where the PID can be defined for testing) or from the system if
// there isn't such.
int64_t GetProcessId() {
GlobalActivityTracker* global = GlobalActivityTracker::Get();
if (global)
return global->process_id();
return GetCurrentProcId();
}
// Finds and reuses a specific allocation or creates a new one.
PersistentMemoryAllocator::Reference AllocateFrom(
PersistentMemoryAllocator* allocator,
uint32_t from_type,
size_t size,
uint32_t to_type) {
PersistentMemoryAllocator::Iterator iter(allocator);
PersistentMemoryAllocator::Reference ref;
while ((ref = iter.GetNextOfType(from_type)) != 0) {
DCHECK_LE(size, allocator->GetAllocSize(ref));
// This can fail if a another thread has just taken it. It is assumed that
// the memory is cleared during the "free" operation.
if (allocator->ChangeType(ref, to_type, from_type, /*clear=*/false))
return ref;
}
return allocator->Allocate(size, to_type);
}
// Determines the previous aligned index.
size_t RoundDownToAlignment(size_t index, size_t alignment) {
return index & (0 - alignment);
}
// Determines the next aligned index.
size_t RoundUpToAlignment(size_t index, size_t alignment) {
return (index + (alignment - 1)) & (0 - alignment);
}
// Converts "tick" timing into wall time.
Time WallTimeFromTickTime(int64_t ticks_start, int64_t ticks, Time time_start) {
return time_start + TimeDelta::FromInternalValue(ticks - ticks_start);
}
} // namespace
union ThreadRef {
int64_t as_id;
#if defined(OS_WIN)
// On Windows, the handle itself is often a pseudo-handle with a common
// value meaning "this thread" and so the thread-id is used. The former
// can be converted to a thread-id with a system call.
PlatformThreadId as_tid;
#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
// On Posix and Fuchsia, the handle is always a unique identifier so no
// conversion needs to be done. However, its value is officially opaque so
// there is no one correct way to convert it to a numerical identifier.
PlatformThreadHandle::Handle as_handle;
#endif
};
OwningProcess::OwningProcess() = default;
OwningProcess::~OwningProcess() = default;
void OwningProcess::Release_Initialize(int64_t pid) {
uint32_t old_id = data_id.load(std::memory_order_acquire);
DCHECK_EQ(0U, old_id);
process_id = pid != 0 ? pid : GetProcessId();
create_stamp = Time::Now().ToInternalValue();
data_id.store(GetNextDataId(), std::memory_order_release);
}
void OwningProcess::SetOwningProcessIdForTesting(int64_t pid, int64_t stamp) {
DCHECK_NE(0U, data_id);
process_id = pid;
create_stamp = stamp;
}
// static
bool OwningProcess::GetOwningProcessId(const void* memory,
int64_t* out_id,
int64_t* out_stamp) {
const OwningProcess* info = reinterpret_cast<const OwningProcess*>(memory);
uint32_t id = info->data_id.load(std::memory_order_acquire);
if (id == 0)
return false;
*out_id = info->process_id;
*out_stamp = info->create_stamp;
return id == info->data_id.load(std::memory_order_seq_cst);
}
// It doesn't matter what is contained in this (though it will be all zeros)
// as only the address of it is important.
const ActivityData kNullActivityData = {};
ActivityData ActivityData::ForThread(const PlatformThreadHandle& handle) {
ThreadRef thread_ref;
thread_ref.as_id = 0; // Zero the union in case other is smaller.
#if defined(OS_WIN)
thread_ref.as_tid = ::GetThreadId(handle.platform_handle());
#elif defined(OS_POSIX)
thread_ref.as_handle = handle.platform_handle();
#endif
return ForThread(thread_ref.as_id);
}
ActivityTrackerMemoryAllocator::ActivityTrackerMemoryAllocator(
PersistentMemoryAllocator* allocator,
uint32_t object_type,
uint32_t object_free_type,
size_t object_size,
size_t cache_size,
bool make_iterable)
: allocator_(allocator),
object_type_(object_type),
object_free_type_(object_free_type),
object_size_(object_size),
cache_size_(cache_size),
make_iterable_(make_iterable),
iterator_(allocator),
cache_values_(new Reference[cache_size]),
cache_used_(0) {
DCHECK(allocator);
}
ActivityTrackerMemoryAllocator::~ActivityTrackerMemoryAllocator() = default;
ActivityTrackerMemoryAllocator::Reference
ActivityTrackerMemoryAllocator::GetObjectReference() {
// First see if there is a cached value that can be returned. This is much
// faster than searching the memory system for free blocks.
while (cache_used_ > 0) {
Reference cached = cache_values_[--cache_used_];
// Change the type of the cached object to the proper type and return it.
// If the type-change fails that means another thread has taken this from
// under us (via the search below) so ignore it and keep trying. Don't
// clear the memory because that was done when the type was made "free".
if (allocator_->ChangeType(cached, object_type_, object_free_type_, false))
return cached;
}
// Fetch the next "free" object from persistent memory. Rather than restart
// the iterator at the head each time and likely waste time going again
// through objects that aren't relevant, the iterator continues from where
// it last left off and is only reset when the end is reached. If the
// returned reference matches |last|, then it has wrapped without finding
// anything.
const Reference last = iterator_.GetLast();
while (true) {
uint32_t type;
Reference found = iterator_.GetNext(&type);
if (found && type == object_free_type_) {
// Found a free object. Change it to the proper type and return it. If
// the type-change fails that means another thread has taken this from
// under us so ignore it and keep trying.
if (allocator_->ChangeType(found, object_type_, object_free_type_, false))
return found;
}
if (found == last) {
// Wrapped. No desired object was found.
break;
}
if (!found) {
// Reached end; start over at the beginning.
iterator_.Reset();
}
}
// No free block was found so instead allocate a new one.
Reference allocated = allocator_->Allocate(object_size_, object_type_);
if (allocated && make_iterable_)
allocator_->MakeIterable(allocated);
return allocated;
}
void ActivityTrackerMemoryAllocator::ReleaseObjectReference(Reference ref) {
// Mark object as free.
bool success = allocator_->ChangeType(ref, object_free_type_, object_type_,
/*clear=*/true);
DCHECK(success);
// Add this reference to our "free" cache if there is space. If not, the type
// has still been changed to indicate that it is free so this (or another)
// thread can find it, albeit more slowly, using the iteration method above.
if (cache_used_ < cache_size_)
cache_values_[cache_used_++] = ref;
}
// static
void Activity::FillFrom(Activity* activity,
const void* program_counter,
const void* origin,
Type type,
const ActivityData& data) {
activity->time_internal = base::TimeTicks::Now().ToInternalValue();
activity->calling_address = reinterpret_cast<uintptr_t>(program_counter);
activity->origin_address = reinterpret_cast<uintptr_t>(origin);
activity->activity_type = type;
activity->data = data;
#if (!defined(OS_NACL) && DCHECK_IS_ON()) || defined(ADDRESS_SANITIZER)
// Create a stacktrace from the current location and get the addresses for
// improved debuggability.
StackTrace stack_trace;
size_t stack_depth;
const void* const* stack_addrs = stack_trace.Addresses(&stack_depth);
// Copy the stack addresses, ignoring the first one (here).
size_t i;
for (i = 1; i < stack_depth && i < kActivityCallStackSize; ++i) {
activity->call_stack[i - 1] = reinterpret_cast<uintptr_t>(stack_addrs[i]);
}
activity->call_stack[i - 1] = 0;
#else
activity->call_stack[0] = 0;
#endif
}
ActivityUserData::TypedValue::TypedValue() = default;
ActivityUserData::TypedValue::TypedValue(const TypedValue& other) = default;
ActivityUserData::TypedValue::~TypedValue() = default;
StringPiece ActivityUserData::TypedValue::Get() const {
DCHECK_EQ(RAW_VALUE, type_);
return long_value_;
}
StringPiece ActivityUserData::TypedValue::GetString() const {
DCHECK_EQ(STRING_VALUE, type_);
return long_value_;
}
bool ActivityUserData::TypedValue::GetBool() const {
DCHECK_EQ(BOOL_VALUE, type_);
return short_value_ != 0;
}
char ActivityUserData::TypedValue::GetChar() const {
DCHECK_EQ(CHAR_VALUE, type_);
return static_cast<char>(short_value_);
}
int64_t ActivityUserData::TypedValue::GetInt() const {
DCHECK_EQ(SIGNED_VALUE, type_);
return static_cast<int64_t>(short_value_);
}
uint64_t ActivityUserData::TypedValue::GetUint() const {
DCHECK_EQ(UNSIGNED_VALUE, type_);
return static_cast<uint64_t>(short_value_);
}
StringPiece ActivityUserData::TypedValue::GetReference() const {
DCHECK_EQ(RAW_VALUE_REFERENCE, type_);
return ref_value_;
}
StringPiece ActivityUserData::TypedValue::GetStringReference() const {
DCHECK_EQ(STRING_VALUE_REFERENCE, type_);
return ref_value_;
}
// These are required because std::atomic is (currently) not a POD type and
// thus clang requires explicit out-of-line constructors and destructors even
// when they do nothing.
ActivityUserData::ValueInfo::ValueInfo() = default;
ActivityUserData::ValueInfo::ValueInfo(ValueInfo&&) = default;
ActivityUserData::ValueInfo::~ValueInfo() = default;
ActivityUserData::MemoryHeader::MemoryHeader() = default;
ActivityUserData::MemoryHeader::~MemoryHeader() = default;
ActivityUserData::FieldHeader::FieldHeader() = default;
ActivityUserData::FieldHeader::~FieldHeader() = default;
ActivityUserData::ActivityUserData() : ActivityUserData(nullptr, 0, -1) {}
ActivityUserData::ActivityUserData(void* memory, size_t size, int64_t pid)
: memory_(reinterpret_cast<char*>(memory)),
available_(RoundDownToAlignment(size, kMemoryAlignment)),
header_(reinterpret_cast<MemoryHeader*>(memory)),
orig_data_id(0),
orig_process_id(0),
orig_create_stamp(0) {
// It's possible that no user data is being stored.
if (!memory_)
return;
static_assert(0 == sizeof(MemoryHeader) % kMemoryAlignment, "invalid header");
DCHECK_LT(sizeof(MemoryHeader), available_);
if (header_->owner.data_id.load(std::memory_order_acquire) == 0)
header_->owner.Release_Initialize(pid);
memory_ += sizeof(MemoryHeader);
available_ -= sizeof(MemoryHeader);
// Make a copy of identifying information for later comparison.
*const_cast<uint32_t*>(&orig_data_id) =
header_->owner.data_id.load(std::memory_order_acquire);
*const_cast<int64_t*>(&orig_process_id) = header_->owner.process_id;
*const_cast<int64_t*>(&orig_create_stamp) = header_->owner.create_stamp;
// If there is already data present, load that. This allows the same class
// to be used for analysis through snapshots.
ImportExistingData();
}
ActivityUserData::~ActivityUserData() = default;
bool ActivityUserData::CreateSnapshot(Snapshot* output_snapshot) const {
DCHECK(output_snapshot);
DCHECK(output_snapshot->empty());
// Find any new data that may have been added by an active instance of this
// class that is adding records.
ImportExistingData();
// Add all the values to the snapshot.
for (const auto& entry : values_) {
TypedValue value;
const size_t size = entry.second.size_ptr->load(std::memory_order_acquire);
value.type_ = entry.second.type;
DCHECK_GE(entry.second.extent, size);
switch (entry.second.type) {
case RAW_VALUE:
case STRING_VALUE:
value.long_value_ =
std::string(reinterpret_cast<char*>(entry.second.memory), size);
break;
case RAW_VALUE_REFERENCE:
case STRING_VALUE_REFERENCE: {
ReferenceRecord* ref =
reinterpret_cast<ReferenceRecord*>(entry.second.memory);
value.ref_value_ = StringPiece(
reinterpret_cast<char*>(static_cast<uintptr_t>(ref->address)),
static_cast<size_t>(ref->size));
} break;
case BOOL_VALUE:
case CHAR_VALUE:
value.short_value_ = *reinterpret_cast<char*>(entry.second.memory);
break;
case SIGNED_VALUE:
case UNSIGNED_VALUE:
value.short_value_ = *reinterpret_cast<uint64_t*>(entry.second.memory);
break;
case END_OF_VALUES: // Included for completeness purposes.
NOTREACHED();
}
auto inserted = output_snapshot->insert(
std::make_pair(entry.second.name.as_string(), std::move(value)));
DCHECK(inserted.second); // True if inserted, false if existed.
}
// Another import attempt will validate that the underlying memory has not
// been reused for another purpose. Entries added since the first import
// will be ignored here but will be returned if another snapshot is created.
ImportExistingData();
if (!memory_) {
output_snapshot->clear();
return false;
}
// Successful snapshot.
return true;
}
const void* ActivityUserData::GetBaseAddress() const {
// The |memory_| pointer advances as elements are written but the |header_|
// value is always at the start of the block so just return that.
return header_;
}
void ActivityUserData::SetOwningProcessIdForTesting(int64_t pid,
int64_t stamp) {
if (!header_)
return;
header_->owner.SetOwningProcessIdForTesting(pid, stamp);
}
// static
bool ActivityUserData::GetOwningProcessId(const void* memory,
int64_t* out_id,
int64_t* out_stamp) {
const MemoryHeader* header = reinterpret_cast<const MemoryHeader*>(memory);
return OwningProcess::GetOwningProcessId(&header->owner, out_id, out_stamp);
}
void ActivityUserData::Set(StringPiece name,
ValueType type,
const void* memory,
size_t size) {
DCHECK_GE(std::numeric_limits<uint8_t>::max(), name.length());
size = std::min(std::numeric_limits<uint16_t>::max() - (kMemoryAlignment - 1),
size);
// It's possible that no user data is being stored.
if (!memory_)
return;
// The storage of a name is limited so use that limit during lookup.
if (name.length() > kMaxUserDataNameLength)
name.set(name.data(), kMaxUserDataNameLength);
ValueInfo* info;
auto existing = values_.find(name);
if (existing != values_.end()) {
info = &existing->second;
} else {
// The name size is limited to what can be held in a single byte but
// because there are not alignment constraints on strings, it's set tight
// against the header. Its extent (the reserved space, even if it's not
// all used) is calculated so that, when pressed against the header, the
// following field will be aligned properly.
size_t name_size = name.length();
size_t name_extent =
RoundUpToAlignment(sizeof(FieldHeader) + name_size, kMemoryAlignment) -
sizeof(FieldHeader);
size_t value_extent = RoundUpToAlignment(size, kMemoryAlignment);
// The "base size" is the size of the header and (padded) string key. Stop
// now if there's not room enough for even this.
size_t base_size = sizeof(FieldHeader) + name_extent;
if (base_size > available_)
return;
// The "full size" is the size for storing the entire value.
size_t full_size = std::min(base_size + value_extent, available_);
// If the value is actually a single byte, see if it can be stuffed at the
// end of the name extent rather than wasting kMemoryAlignment bytes.
if (size == 1 && name_extent > name_size) {
full_size = base_size;
--name_extent;
--base_size;
}
// Truncate the stored size to the amount of available memory. Stop now if
// there's not any room for even part of the value.
if (size != 0) {
size = std::min(full_size - base_size, size);
if (size == 0)
return;
}
// Allocate a chunk of memory.
FieldHeader* header = reinterpret_cast<FieldHeader*>(memory_);
memory_ += full_size;
available_ -= full_size;
// Datafill the header and name records. Memory must be zeroed. The |type|
// is written last, atomically, to release all the other values.
DCHECK_EQ(END_OF_VALUES, header->type.load(std::memory_order_relaxed));
DCHECK_EQ(0, header->value_size.load(std::memory_order_relaxed));
header->name_size = static_cast<uint8_t>(name_size);
header->record_size = full_size;
char* name_memory = reinterpret_cast<char*>(header) + sizeof(FieldHeader);
void* value_memory =
reinterpret_cast<char*>(header) + sizeof(FieldHeader) + name_extent;
memcpy(name_memory, name.data(), name_size);
header->type.store(type, std::memory_order_release);
// Create an entry in |values_| so that this field can be found and changed
// later on without having to allocate new entries.
StringPiece persistent_name(name_memory, name_size);
auto inserted =
values_.insert(std::make_pair(persistent_name, ValueInfo()));
DCHECK(inserted.second); // True if inserted, false if existed.
info = &inserted.first->second;
info->name = persistent_name;
info->memory = value_memory;
info->size_ptr = &header->value_size;
info->extent = full_size - sizeof(FieldHeader) - name_extent;
info->type = type;
}
// Copy the value data to storage. The |size| is written last, atomically, to
// release the copied data. Until then, a parallel reader will just ignore
// records with a zero size.
DCHECK_EQ(type, info->type);
size = std::min(size, info->extent);
info->size_ptr->store(0, std::memory_order_seq_cst);
memcpy(info->memory, memory, size);
info->size_ptr->store(size, std::memory_order_release);
}
void ActivityUserData::SetReference(StringPiece name,
ValueType type,
const void* memory,
size_t size) {
ReferenceRecord rec;
rec.address = reinterpret_cast<uintptr_t>(memory);
rec.size = size;
Set(name, type, &rec, sizeof(rec));
}
void ActivityUserData::ImportExistingData() const {
// It's possible that no user data is being stored.
if (!memory_)
return;
while (available_ > sizeof(FieldHeader)) {
FieldHeader* header = reinterpret_cast<FieldHeader*>(memory_);
ValueType type =
static_cast<ValueType>(header->type.load(std::memory_order_acquire));
if (type == END_OF_VALUES)
return;
if (header->record_size > available_)
return;
size_t value_offset = RoundUpToAlignment(
sizeof(FieldHeader) + header->name_size, kMemoryAlignment);
if (header->record_size == value_offset &&
header->value_size.load(std::memory_order_relaxed) == 1) {
value_offset -= 1;
}
if (value_offset + header->value_size > header->record_size)
return;
ValueInfo info;
info.name = StringPiece(memory_ + sizeof(FieldHeader), header->name_size);
info.type = type;
info.memory = memory_ + value_offset;
info.size_ptr = &header->value_size;
info.extent = header->record_size - value_offset;
StringPiece key(info.name);
values_.insert(std::make_pair(key, std::move(info)));
memory_ += header->record_size;
available_ -= header->record_size;
}
// Check if memory has been completely reused.
if (header_->owner.data_id.load(std::memory_order_acquire) != orig_data_id ||
header_->owner.process_id != orig_process_id ||
header_->owner.create_stamp != orig_create_stamp) {
memory_ = nullptr;
values_.clear();
}
}
// This information is kept for every thread that is tracked. It is filled
// the very first time the thread is seen. All fields must be of exact sizes
// so there is no issue moving between 32 and 64-bit builds.
struct ThreadActivityTracker::Header {
// Defined in .h for analyzer access. Increment this if structure changes!
static constexpr uint32_t kPersistentTypeId =
GlobalActivityTracker::kTypeIdActivityTracker;
// Expected size for 32/64-bit check.
static constexpr size_t kExpectedInstanceSize =
OwningProcess::kExpectedInstanceSize + Activity::kExpectedInstanceSize +
72;
// This information uniquely identifies a process.
OwningProcess owner;
// The thread-id (thread_ref.as_id) to which this data belongs. This number
// is not guaranteed to mean anything but combined with the process-id from
// OwningProcess is unique among all active trackers.
ThreadRef thread_ref;
// The start-time and start-ticks when the data was created. Each activity
// record has a |time_internal| value that can be converted to a "wall time"
// with these two values.
int64_t start_time;
int64_t start_ticks;
// The number of Activity slots (spaces that can hold an Activity) that
// immediately follow this structure in memory.
uint32_t stack_slots;
// Some padding to keep everything 64-bit aligned.
uint32_t padding;
// The current depth of the stack. This may be greater than the number of
// slots. If the depth exceeds the number of slots, the newest entries
// won't be recorded.
std::atomic<uint32_t> current_depth;
// A memory location used to indicate if changes have been made to the data
// that would invalidate an in-progress read of its contents. The active
// tracker will increment the value whenever something gets popped from the
// stack. A monitoring tracker can check the value before and after access
// to know, if it's still the same, that the contents didn't change while
// being copied.
std::atomic<uint32_t> data_version;
// The last "exception" activity. This can't be stored on the stack because
// that could get popped as things unwind.
Activity last_exception;
// The name of the thread (up to a maximum length). Dynamic-length names
// are not practical since the memory has to come from the same persistent
// allocator that holds this structure and to which this object has no
// reference.
char thread_name[32];
};
ThreadActivityTracker::Snapshot::Snapshot() = default;
ThreadActivityTracker::Snapshot::~Snapshot() = default;
ThreadActivityTracker::ScopedActivity::ScopedActivity(
ThreadActivityTracker* tracker,
const void* program_counter,
const void* origin,
Activity::Type type,
const ActivityData& data)
: tracker_(tracker) {
if (tracker_)
activity_id_ = tracker_->PushActivity(program_counter, origin, type, data);
}
ThreadActivityTracker::ScopedActivity::~ScopedActivity() {
if (tracker_)
tracker_->PopActivity(activity_id_);
}
void ThreadActivityTracker::ScopedActivity::ChangeTypeAndData(
Activity::Type type,
const ActivityData& data) {
if (tracker_)
tracker_->ChangeActivity(activity_id_, type, data);
}
ThreadActivityTracker::ThreadActivityTracker(void* base, size_t size)
: header_(static_cast<Header*>(base)),
stack_(reinterpret_cast<Activity*>(reinterpret_cast<char*>(base) +
sizeof(Header))),
#if DCHECK_IS_ON()
thread_id_(PlatformThreadRef()),
#endif
stack_slots_(
static_cast<uint32_t>((size - sizeof(Header)) / sizeof(Activity))) {
// Verify the parameters but fail gracefully if they're not valid so that
// production code based on external inputs will not crash. IsValid() will
// return false in this case.
if (!base ||
// Ensure there is enough space for the header and at least a few records.
size < sizeof(Header) + kMinStackDepth * sizeof(Activity) ||
// Ensure that the |stack_slots_| calculation didn't overflow.
(size - sizeof(Header)) / sizeof(Activity) >
std::numeric_limits<uint32_t>::max()) {
NOTREACHED();
return;
}
// Ensure that the thread reference doesn't exceed the size of the ID number.
// This won't compile at the global scope because Header is a private struct.
static_assert(
sizeof(header_->thread_ref) == sizeof(header_->thread_ref.as_id),
"PlatformThreadHandle::Handle is too big to hold in 64-bit ID");
// Ensure that the alignment of Activity.data is properly aligned to a
// 64-bit boundary so there are no interoperability-issues across cpu
// architectures.
static_assert(offsetof(Activity, data) % sizeof(uint64_t) == 0,
"ActivityData.data is not 64-bit aligned");
// Provided memory should either be completely initialized or all zeros.
if (header_->owner.data_id.load(std::memory_order_relaxed) == 0) {
// This is a new file. Double-check other fields and then initialize.
DCHECK_EQ(0, header_->owner.process_id);
DCHECK_EQ(0, header_->owner.create_stamp);
DCHECK_EQ(0, header_->thread_ref.as_id);
DCHECK_EQ(0, header_->start_time);
DCHECK_EQ(0, header_->start_ticks);
DCHECK_EQ(0U, header_->stack_slots);
DCHECK_EQ(0U, header_->current_depth.load(std::memory_order_relaxed));
DCHECK_EQ(0U, header_->data_version.load(std::memory_order_relaxed));
DCHECK_EQ(0, stack_[0].time_internal);
DCHECK_EQ(0U, stack_[0].origin_address);
DCHECK_EQ(0U, stack_[0].call_stack[0]);
DCHECK_EQ(0U, stack_[0].data.task.sequence_id);
#if defined(OS_WIN)
header_->thread_ref.as_tid = PlatformThread::CurrentId();
#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
header_->thread_ref.as_handle =
PlatformThread::CurrentHandle().platform_handle();
#endif
header_->start_time = base::Time::Now().ToInternalValue();
header_->start_ticks = base::TimeTicks::Now().ToInternalValue();
header_->stack_slots = stack_slots_;
strlcpy(header_->thread_name, PlatformThread::GetName(),
sizeof(header_->thread_name));
// This is done last so as to guarantee that everything above is "released"
// by the time this value gets written.
header_->owner.Release_Initialize();
valid_ = true;
DCHECK(IsValid());
} else {
// This is a file with existing data. Perform basic consistency checks.
valid_ = true;
valid_ = IsValid();
}
}
ThreadActivityTracker::~ThreadActivityTracker() = default;
ThreadActivityTracker::ActivityId ThreadActivityTracker::PushActivity(
const void* program_counter,
const void* origin,
Activity::Type type,
const ActivityData& data) {
// A thread-checker creates a lock to check the thread-id which means
// re-entry into this code if lock acquisitions are being tracked.
DCHECK(type == Activity::ACT_LOCK_ACQUIRE || CalledOnValidThread());
// Get the current depth of the stack. No access to other memory guarded
// by this variable is done here so a "relaxed" load is acceptable.
uint32_t depth = header_->current_depth.load(std::memory_order_relaxed);
// Handle the case where the stack depth has exceeded the storage capacity.
// Extra entries will be lost leaving only the base of the stack.
if (depth >= stack_slots_) {
// Since no other threads modify the data, no compare/exchange is needed.
// Since no other memory is being modified, a "relaxed" store is acceptable.
header_->current_depth.store(depth + 1, std::memory_order_relaxed);
return depth;
}
// Get a pointer to the next activity and load it. No atomicity is required
// here because the memory is known only to this thread. It will be made
// known to other threads once the depth is incremented.
Activity::FillFrom(&stack_[depth], program_counter, origin, type, data);
// Save the incremented depth. Because this guards |activity| memory filled
// above that may be read by another thread once the recorded depth changes,
// a "release" store is required.
header_->current_depth.store(depth + 1, std::memory_order_release);
// The current depth is used as the activity ID because it simply identifies
// an entry. Once an entry is pop'd, it's okay to reuse the ID.
return depth;
}
void ThreadActivityTracker::ChangeActivity(ActivityId id,
Activity::Type type,
const ActivityData& data) {
DCHECK(CalledOnValidThread());
DCHECK(type != Activity::ACT_NULL || &data != &kNullActivityData);
DCHECK_LT(id, header_->current_depth.load(std::memory_order_acquire));
// Update the information if it is being recorded (i.e. within slot limit).
if (id < stack_slots_) {
Activity* activity = &stack_[id];
if (type != Activity::ACT_NULL) {
DCHECK_EQ(activity->activity_type & Activity::ACT_CATEGORY_MASK,
type & Activity::ACT_CATEGORY_MASK);
activity->activity_type = type;
}
if (&data != &kNullActivityData)
activity->data = data;
}
}
void ThreadActivityTracker::PopActivity(ActivityId id) {
// Do an atomic decrement of the depth. No changes to stack entries guarded
// by this variable are done here so a "relaxed" operation is acceptable.
// |depth| will receive the value BEFORE it was modified which means the
// return value must also be decremented. The slot will be "free" after
// this call but since only a single thread can access this object, the
// data will remain valid until this method returns or calls outside.
uint32_t depth =
header_->current_depth.fetch_sub(1, std::memory_order_relaxed) - 1;
// Validate that everything is running correctly.
DCHECK_EQ(id, depth);
// A thread-checker creates a lock to check the thread-id which means
// re-entry into this code if lock acquisitions are being tracked.
DCHECK(stack_[depth].activity_type == Activity::ACT_LOCK_ACQUIRE ||
CalledOnValidThread());
// The stack has shrunk meaning that some other thread trying to copy the
// contents for reporting purposes could get bad data. Increment the data
// version so that it con tell that things have changed. This needs to
// happen after the atomic |depth| operation above so a "release" store
// is required.
header_->data_version.fetch_add(1, std::memory_order_release);
}
std::unique_ptr<ActivityUserData> ThreadActivityTracker::GetUserData(
ActivityId id,
ActivityTrackerMemoryAllocator* allocator) {
// Don't allow user data for lock acquisition as recursion may occur.
if (stack_[id].activity_type == Activity::ACT_LOCK_ACQUIRE) {
NOTREACHED();
return std::make_unique<ActivityUserData>();
}
// User-data is only stored for activities actually held in the stack.
if (id >= stack_slots_)
return std::make_unique<ActivityUserData>();
// Create and return a real UserData object.
return CreateUserDataForActivity(&stack_[id], allocator);
}
bool ThreadActivityTracker::HasUserData(ActivityId id) {
// User-data is only stored for activities actually held in the stack.
return (id < stack_slots_ && stack_[id].user_data_ref);
}
void ThreadActivityTracker::ReleaseUserData(
ActivityId id,
ActivityTrackerMemoryAllocator* allocator) {
// User-data is only stored for activities actually held in the stack.
if (id < stack_slots_ && stack_[id].user_data_ref) {
allocator->ReleaseObjectReference(stack_[id].user_data_ref);
stack_[id].user_data_ref = 0;
}
}
void ThreadActivityTracker::RecordExceptionActivity(const void* program_counter,
const void* origin,
Activity::Type type,
const ActivityData& data) {
// A thread-checker creates a lock to check the thread-id which means
// re-entry into this code if lock acquisitions are being tracked.
DCHECK(CalledOnValidThread());
// Fill the reusable exception activity.
Activity::FillFrom(&header_->last_exception, program_counter, origin, type,
data);
// The data has changed meaning that some other thread trying to copy the
// contents for reporting purposes could get bad data.
header_->data_version.fetch_add(1, std::memory_order_relaxed);
}
bool ThreadActivityTracker::IsValid() const {
if (header_->owner.data_id.load(std::memory_order_acquire) == 0 ||
header_->owner.process_id == 0 || header_->thread_ref.as_id == 0 ||
header_->start_time == 0 || header_->start_ticks == 0 ||
header_->stack_slots != stack_slots_ ||
header_->thread_name[sizeof(header_->thread_name) - 1] != '\0') {
return false;
}
return valid_;
}
bool ThreadActivityTracker::CreateSnapshot(Snapshot* output_snapshot) const {
DCHECK(output_snapshot);
// There is no "called on valid thread" check for this method as it can be
// called from other threads or even other processes. It is also the reason
// why atomic operations must be used in certain places above.
// It's possible for the data to change while reading it in such a way that it
// invalidates the read. Make several attempts but don't try forever.
const int kMaxAttempts = 10;
uint32_t depth;
// Stop here if the data isn't valid.
if (!IsValid())
return false;
// Allocate the maximum size for the stack so it doesn't have to be done
// during the time-sensitive snapshot operation. It is shrunk once the
// actual size is known.
output_snapshot->activity_stack.reserve(stack_slots_);
for (int attempt = 0; attempt < kMaxAttempts; ++attempt) {
// Remember the data IDs to ensure nothing is replaced during the snapshot
// operation. Use "acquire" so that all the non-atomic fields of the
// structure are valid (at least at the current moment in time).
const uint32_t starting_id =
header_->owner.data_id.load(std::memory_order_acquire);
const int64_t starting_create_stamp = header_->owner.create_stamp;
const int64_t starting_process_id = header_->owner.process_id;
const int64_t starting_thread_id = header_->thread_ref.as_id;
// Note the current |data_version| so it's possible to detect at the end
// that nothing has changed since copying the data began. A "cst" operation
// is required to ensure it occurs before everything else. Using "cst"
// memory ordering is relatively expensive but this is only done during
// analysis so doesn't directly affect the worker threads.
const uint32_t pre_version =
header_->data_version.load(std::memory_order_seq_cst);
// Fetching the current depth also "acquires" the contents of the stack.
depth = header_->current_depth.load(std::memory_order_acquire);
uint32_t count = std::min(depth, stack_slots_);
output_snapshot->activity_stack.resize(count);
if (count > 0) {
// Copy the existing contents. Memcpy is used for speed.
memcpy(&output_snapshot->activity_stack[0], stack_,
count * sizeof(Activity));
}
// Capture the last exception.
memcpy(&output_snapshot->last_exception, &header_->last_exception,
sizeof(Activity));
// TODO(bcwhite): Snapshot other things here.
// Retry if something changed during the copy. A "cst" operation ensures
// it must happen after all the above operations.
if (header_->data_version.load(std::memory_order_seq_cst) != pre_version)
continue;
// Stack copied. Record it's full depth.
output_snapshot->activity_stack_depth = depth;
// Get the general thread information.
output_snapshot->thread_name =
std::string(header_->thread_name, sizeof(header_->thread_name) - 1);
output_snapshot->create_stamp = header_->owner.create_stamp;
output_snapshot->thread_id = header_->thread_ref.as_id;
output_snapshot->process_id = header_->owner.process_id;
// All characters of the thread-name buffer were copied so as to not break
// if the trailing NUL were missing. Now limit the length if the actual
// name is shorter.
output_snapshot->thread_name.resize(
strlen(output_snapshot->thread_name.c_str()));
// If the data ID has changed then the tracker has exited and the memory
// reused by a new one. Try again.
if (header_->owner.data_id.load(std::memory_order_seq_cst) != starting_id ||
output_snapshot->create_stamp != starting_create_stamp ||
output_snapshot->process_id != starting_process_id ||
output_snapshot->thread_id != starting_thread_id) {
continue;
}
// Only successful if the data is still valid once everything is done since
// it's possible for the thread to end somewhere in the middle and all its
// values become garbage.
if (!IsValid())
return false;
// Change all the timestamps in the activities from "ticks" to "wall" time.
const Time start_time = Time::FromInternalValue(header_->start_time);
const int64_t start_ticks = header_->start_ticks;
for (Activity& activity : output_snapshot->activity_stack) {
activity.time_internal =
WallTimeFromTickTime(start_ticks, activity.time_internal, start_time)
.ToInternalValue();
}
output_snapshot->last_exception.time_internal =
WallTimeFromTickTime(start_ticks,
output_snapshot->last_exception.time_internal,
start_time)
.ToInternalValue();
// Success!
return true;
}
// Too many attempts.
return false;
}
const void* ThreadActivityTracker::GetBaseAddress() {
return header_;
}
uint32_t ThreadActivityTracker::GetDataVersionForTesting() {
return header_->data_version.load(std::memory_order_relaxed);
}
void ThreadActivityTracker::SetOwningProcessIdForTesting(int64_t pid,
int64_t stamp) {
header_->owner.SetOwningProcessIdForTesting(pid, stamp);
}
// static
bool ThreadActivityTracker::GetOwningProcessId(const void* memory,
int64_t* out_id,
int64_t* out_stamp) {
const Header* header = reinterpret_cast<const Header*>(memory);
return OwningProcess::GetOwningProcessId(&header->owner, out_id, out_stamp);
}
// static
size_t ThreadActivityTracker::SizeForStackDepth(int stack_depth) {
return static_cast<size_t>(stack_depth) * sizeof(Activity) + sizeof(Header);
}
bool ThreadActivityTracker::CalledOnValidThread() {
#if DCHECK_IS_ON()
return thread_id_ == PlatformThreadRef();
#else
return true;
#endif
}
std::unique_ptr<ActivityUserData>
ThreadActivityTracker::CreateUserDataForActivity(
Activity* activity,
ActivityTrackerMemoryAllocator* allocator) {
DCHECK_EQ(0U, activity->user_data_ref);
PersistentMemoryAllocator::Reference ref = allocator->GetObjectReference();
void* memory = allocator->GetAsArray<char>(ref, kUserDataSize);
if (memory) {
std::unique_ptr<ActivityUserData> user_data =
std::make_unique<ActivityUserData>(memory, kUserDataSize);
activity->user_data_ref = ref;
activity->user_data_id = user_data->id();
return user_data;
}
// Return a dummy object that will still accept (but ignore) Set() calls.
return std::make_unique<ActivityUserData>();
}
// The instantiation of the GlobalActivityTracker object.
// The object held here will obviously not be destructed at process exit
// but that's best since PersistentMemoryAllocator objects (that underlie
// GlobalActivityTracker objects) are explicitly forbidden from doing anything
// essential at exit anyway due to the fact that they depend on data managed
// elsewhere and which could be destructed first. An AtomicWord is used instead
// of std::atomic because the latter can create global ctors and dtors.
subtle::AtomicWord GlobalActivityTracker::g_tracker_ = 0;
GlobalActivityTracker::ModuleInfo::ModuleInfo() = default;
GlobalActivityTracker::ModuleInfo::ModuleInfo(ModuleInfo&& rhs) = default;
GlobalActivityTracker::ModuleInfo::ModuleInfo(const ModuleInfo& rhs) = default;
GlobalActivityTracker::ModuleInfo::~ModuleInfo() = default;
GlobalActivityTracker::ModuleInfo& GlobalActivityTracker::ModuleInfo::operator=(
ModuleInfo&& rhs) = default;
GlobalActivityTracker::ModuleInfo& GlobalActivityTracker::ModuleInfo::operator=(
const ModuleInfo& rhs) = default;
GlobalActivityTracker::ModuleInfoRecord::ModuleInfoRecord() = default;
GlobalActivityTracker::ModuleInfoRecord::~ModuleInfoRecord() = default;
bool GlobalActivityTracker::ModuleInfoRecord::DecodeTo(
GlobalActivityTracker::ModuleInfo* info,
size_t record_size) const {
// Get the current "changes" indicator, acquiring all the other values.
uint32_t current_changes = changes.load(std::memory_order_acquire);
// Copy out the dynamic information.
info->is_loaded = loaded != 0;
info->address = static_cast<uintptr_t>(address);
info->load_time = load_time;
// Check to make sure no information changed while being read. A "seq-cst"
// operation is expensive but is only done during analysis and it's the only
// way to ensure this occurs after all the accesses above. If changes did
// occur then return a "not loaded" result so that |size| and |address|
// aren't expected to be accurate.
if ((current_changes & kModuleInformationChanging) != 0 ||
changes.load(std::memory_order_seq_cst) != current_changes) {
info->is_loaded = false;
}
// Copy out the static information. These never change so don't have to be
// protected by the atomic |current_changes| operations.
info->size = static_cast<size_t>(size);
info->timestamp = timestamp;
info->age = age;
memcpy(info->identifier, identifier, sizeof(info->identifier));
if (offsetof(ModuleInfoRecord, pickle) + pickle_size > record_size)
return false;
Pickle pickler(pickle, pickle_size);
PickleIterator iter(pickler);
return iter.ReadString(&info->file) && iter.ReadString(&info->debug_file);
}
GlobalActivityTracker::ModuleInfoRecord*
GlobalActivityTracker::ModuleInfoRecord::CreateFrom(
const GlobalActivityTracker::ModuleInfo& info,
PersistentMemoryAllocator* allocator) {
Pickle pickler;
pickler.WriteString(info.file);
pickler.WriteString(info.debug_file);
size_t required_size = offsetof(ModuleInfoRecord, pickle) + pickler.size();
ModuleInfoRecord* record = allocator->New<ModuleInfoRecord>(required_size);
if (!record)
return nullptr;
// These fields never changes and are done before the record is made
// iterable so no thread protection is necessary.
record->size = info.size;
record->timestamp = info.timestamp;
record->age = info.age;
memcpy(record->identifier, info.identifier, sizeof(identifier));
memcpy(record->pickle, pickler.data(), pickler.size());
record->pickle_size = pickler.size();
record->changes.store(0, std::memory_order_relaxed);
// Initialize the owner info.
record->owner.Release_Initialize();
// Now set those fields that can change.
bool success = record->UpdateFrom(info);
DCHECK(success);
return record;
}
bool GlobalActivityTracker::ModuleInfoRecord::UpdateFrom(
const GlobalActivityTracker::ModuleInfo& info) {
// Updates can occur after the record is made visible so make changes atomic.
// A "strong" exchange ensures no false failures.
uint32_t old_changes = changes.load(std::memory_order_relaxed);
uint32_t new_changes = old_changes | kModuleInformationChanging;
if ((old_changes & kModuleInformationChanging) != 0 ||
!changes.compare_exchange_strong(old_changes, new_changes,
std::memory_order_acquire,
std::memory_order_acquire)) {
NOTREACHED() << "Multiple sources are updating module information.";
return false;
}
loaded = info.is_loaded ? 1 : 0;
address = info.address;
load_time = Time::Now().ToInternalValue();
bool success = changes.compare_exchange_strong(new_changes, old_changes + 1,
std::memory_order_release,
std::memory_order_relaxed);
DCHECK(success);
return true;
}
GlobalActivityTracker::ScopedThreadActivity::ScopedThreadActivity(
const void* program_counter,
const void* origin,
Activity::Type type,
const ActivityData& data,
bool lock_allowed)
: ThreadActivityTracker::ScopedActivity(GetOrCreateTracker(lock_allowed),
program_counter,
origin,
type,
data) {}
GlobalActivityTracker::ScopedThreadActivity::~ScopedThreadActivity() {
if (tracker_ && tracker_->HasUserData(activity_id_)) {
GlobalActivityTracker* global = GlobalActivityTracker::Get();
AutoLock lock(global->user_data_allocator_lock_);
tracker_->ReleaseUserData(activity_id_, &global->user_data_allocator_);
}
}
ActivityUserData& GlobalActivityTracker::ScopedThreadActivity::user_data() {
if (!user_data_) {
if (tracker_) {
GlobalActivityTracker* global = GlobalActivityTracker::Get();
AutoLock lock(global->user_data_allocator_lock_);
user_data_ =
tracker_->GetUserData(activity_id_, &global->user_data_allocator_);
} else {
user_data_ = std::make_unique<ActivityUserData>();
}
}
return *user_data_;
}
GlobalActivityTracker::ThreadSafeUserData::ThreadSafeUserData(void* memory,
size_t size,
int64_t pid)
: ActivityUserData(memory, size, pid) {}
GlobalActivityTracker::ThreadSafeUserData::~ThreadSafeUserData() = default;
void GlobalActivityTracker::ThreadSafeUserData::Set(StringPiece name,
ValueType type,
const void* memory,
size_t size) {
AutoLock lock(data_lock_);
ActivityUserData::Set(name, type, memory, size);
}
GlobalActivityTracker::ManagedActivityTracker::ManagedActivityTracker(
PersistentMemoryAllocator::Reference mem_reference,
void* base,
size_t size)
: ThreadActivityTracker(base, size),
mem_reference_(mem_reference),
mem_base_(base) {}
GlobalActivityTracker::ManagedActivityTracker::~ManagedActivityTracker() {
// The global |g_tracker_| must point to the owner of this class since all
// objects of this type must be destructed before |g_tracker_| can be changed
// (something that only occurs in tests).
DCHECK(g_tracker_);
GlobalActivityTracker::Get()->ReturnTrackerMemory(this);
}
void GlobalActivityTracker::CreateWithAllocator(
std::unique_ptr<PersistentMemoryAllocator> allocator,
int stack_depth,
int64_t process_id) {
// There's no need to do anything with the result. It is self-managing.
GlobalActivityTracker* global_tracker =
new GlobalActivityTracker(std::move(allocator), stack_depth, process_id);
// Create a tracker for this thread since it is known.
global_tracker->CreateTrackerForCurrentThread();
}
#if !defined(OS_NACL) && !defined(STARBOARD)
// static
bool GlobalActivityTracker::CreateWithFile(const FilePath& file_path,
size_t size,
uint64_t id,
StringPiece name,
int stack_depth) {
DCHECK(!file_path.empty());
DCHECK_GE(static_cast<uint64_t>(std::numeric_limits<int64_t>::max()), size);
// Create and map the file into memory and make it globally available.
std::unique_ptr<MemoryMappedFile> mapped_file(new MemoryMappedFile());
bool success = mapped_file->Initialize(
File(file_path, File::FLAG_CREATE_ALWAYS | File::FLAG_READ |
File::FLAG_WRITE | File::FLAG_SHARE_DELETE),
{0, size}, MemoryMappedFile::READ_WRITE_EXTEND);
if (!success)
return false;
if (!FilePersistentMemoryAllocator::IsFileAcceptable(*mapped_file, false))
return false;
CreateWithAllocator(std::make_unique<FilePersistentMemoryAllocator>(
std::move(mapped_file), size, id, name, false),
stack_depth, 0);
return true;
}
#endif // !defined(OS_NACL)
// static
bool GlobalActivityTracker::CreateWithLocalMemory(size_t size,
uint64_t id,
StringPiece name,
int stack_depth,
int64_t process_id) {
CreateWithAllocator(
std::make_unique<LocalPersistentMemoryAllocator>(size, id, name),
stack_depth, process_id);
return true;
}
#if !defined(STARBOARD)
// static
bool GlobalActivityTracker::CreateWithSharedMemory(
std::unique_ptr<SharedMemory> shm,
uint64_t id,
StringPiece name,
int stack_depth) {
if (shm->mapped_size() == 0 ||
!SharedPersistentMemoryAllocator::IsSharedMemoryAcceptable(*shm)) {
return false;
}
CreateWithAllocator(std::make_unique<SharedPersistentMemoryAllocator>(
std::move(shm), id, name, false),
stack_depth, 0);
return true;
}
// static
bool GlobalActivityTracker::CreateWithSharedMemoryHandle(
const SharedMemoryHandle& handle,
size_t size,
uint64_t id,
StringPiece name,
int stack_depth) {
std::unique_ptr<SharedMemory> shm(
new SharedMemory(handle, /*readonly=*/false));
if (!shm->Map(size))
return false;
return CreateWithSharedMemory(std::move(shm), id, name, stack_depth);
}
#endif
// static
void GlobalActivityTracker::SetForTesting(
std::unique_ptr<GlobalActivityTracker> tracker) {
CHECK(!subtle::NoBarrier_Load(&g_tracker_));
subtle::Release_Store(&g_tracker_,
reinterpret_cast<uintptr_t>(tracker.release()));
}
// static
std::unique_ptr<GlobalActivityTracker>
GlobalActivityTracker::ReleaseForTesting() {
GlobalActivityTracker* tracker = Get();
if (!tracker)
return nullptr;
// Thread trackers assume that the global tracker is present for some
// operations so ensure that there aren't any.
tracker->ReleaseTrackerForCurrentThreadForTesting();
DCHECK_EQ(0, tracker->thread_tracker_count_.load(std::memory_order_relaxed));
subtle::Release_Store(&g_tracker_, 0);
return WrapUnique(tracker);
}
ThreadActivityTracker* GlobalActivityTracker::CreateTrackerForCurrentThread() {
// It is not safe to use TLS once TLS has been destroyed.
if (base::ThreadLocalStorage::HasBeenDestroyed())
return nullptr;
DCHECK(!this_thread_tracker_.Get());
PersistentMemoryAllocator::Reference mem_reference;
{
base::AutoLock autolock(thread_tracker_allocator_lock_);
mem_reference = thread_tracker_allocator_.GetObjectReference();
}
if (!mem_reference) {
// Failure. This shouldn't happen. But be graceful if it does, probably
// because the underlying allocator wasn't given enough memory to satisfy
// to all possible requests.
NOTREACHED();
// Report the thread-count at which the allocator was full so that the
// failure can be seen and underlying memory resized appropriately.
UMA_HISTOGRAM_COUNTS_1000(
"ActivityTracker.ThreadTrackers.MemLimitTrackerCount",
thread_tracker_count_.load(std::memory_order_relaxed));
// Return null, just as if tracking wasn't enabled.
return nullptr;
}
// Convert the memory block found above into an actual memory address.
// Doing the conversion as a Header object enacts the 32/64-bit size
// consistency checks which would not otherwise be done. Unfortunately,
// some older compilers and MSVC don't have standard-conforming definitions
// of std::atomic which cause it not to be plain-old-data. Don't check on
// those platforms assuming that the checks on other platforms will be
// sufficient.
// TODO(bcwhite): Review this after major compiler releases.
DCHECK(mem_reference);
void* mem_base;
mem_base =
allocator_->GetAsObject<ThreadActivityTracker::Header>(mem_reference);
DCHECK(mem_base);
DCHECK_LE(stack_memory_size_, allocator_->GetAllocSize(mem_reference));
// Create a tracker with the acquired memory and set it as the tracker
// for this particular thread in thread-local-storage.
ManagedActivityTracker* tracker =
new ManagedActivityTracker(mem_reference, mem_base, stack_memory_size_);
DCHECK(tracker->IsValid());
this_thread_tracker_.Set(tracker);
int old_count = thread_tracker_count_.fetch_add(1, std::memory_order_relaxed);
UMA_HISTOGRAM_EXACT_LINEAR("ActivityTracker.ThreadTrackers.Count",
old_count + 1, static_cast<int>(kMaxThreadCount));
return tracker;
}
void GlobalActivityTracker::ReleaseTrackerForCurrentThreadForTesting() {
ThreadActivityTracker* tracker =
reinterpret_cast<ThreadActivityTracker*>(this_thread_tracker_.Get());
if (tracker) {
this_thread_tracker_.Set(nullptr);
delete tracker;
}
}
void GlobalActivityTracker::SetBackgroundTaskRunner(
const scoped_refptr<TaskRunner>& runner) {
AutoLock lock(global_tracker_lock_);
background_task_runner_ = runner;
}
void GlobalActivityTracker::SetProcessExitCallback(
ProcessExitCallback callback) {
AutoLock lock(global_tracker_lock_);
process_exit_callback_ = callback;
}
void GlobalActivityTracker::RecordProcessLaunch(
ProcessId process_id,
const FilePath::StringType& cmd) {
const int64_t pid = process_id;
DCHECK_NE(GetProcessId(), pid);
DCHECK_NE(0, pid);
base::AutoLock lock(global_tracker_lock_);
if (base::ContainsKey(known_processes_, pid)) {
// TODO(bcwhite): Measure this in UMA.
NOTREACHED() << "Process #" << process_id
<< " was previously recorded as \"launched\""
<< " with no corresponding exit.\n"
<< known_processes_[pid];
known_processes_.erase(pid);
}
#if defined(OS_WIN)
known_processes_.insert(std::make_pair(pid, UTF16ToUTF8(cmd)));
#else
known_processes_.insert(std::make_pair(pid, cmd));
#endif
}
void GlobalActivityTracker::RecordProcessLaunch(
ProcessId process_id,
const FilePath::StringType& exe,
const FilePath::StringType& args) {
if (exe.find(FILE_PATH_LITERAL(" "))) {
RecordProcessLaunch(process_id,
FilePath::StringType(FILE_PATH_LITERAL("\"")) + exe +
FILE_PATH_LITERAL("\" ") + args);
} else {
RecordProcessLaunch(process_id, exe + FILE_PATH_LITERAL(' ') + args);
}
}
void GlobalActivityTracker::RecordProcessExit(ProcessId process_id,
int exit_code) {
const int64_t pid = process_id;
DCHECK_NE(GetProcessId(), pid);
DCHECK_NE(0, pid);
scoped_refptr<TaskRunner> task_runner;
std::string command_line;
{
base::AutoLock lock(global_tracker_lock_);
task_runner = background_task_runner_;
auto found = known_processes_.find(pid);
if (found != known_processes_.end()) {
command_line = std::move(found->second);
known_processes_.erase(found);
} else {
DLOG(ERROR) << "Recording exit of unknown process #" << process_id;
}
}
// Use the current time to differentiate the process that just exited
// from any that might be created in the future with the same ID.
int64_t now_stamp = Time::Now().ToInternalValue();
// The persistent allocator is thread-safe so run the iteration and
// adjustments on a worker thread if one was provided.
if (task_runner && !task_runner->RunsTasksInCurrentSequence()) {
task_runner->PostTask(
FROM_HERE,
BindOnce(&GlobalActivityTracker::CleanupAfterProcess, Unretained(this),
pid, now_stamp, exit_code, std::move(command_line)));
return;
}
CleanupAfterProcess(pid, now_stamp, exit_code, std::move(command_line));
}
void GlobalActivityTracker::SetProcessPhase(ProcessPhase phase) {
process_data().SetInt(kProcessPhaseDataKey, phase);
}
void GlobalActivityTracker::CleanupAfterProcess(int64_t process_id,
int64_t exit_stamp,
int exit_code,
std::string&& command_line) {
// The process may not have exited cleanly so its necessary to go through
// all the data structures it may have allocated in the persistent memory
// segment and mark them as "released". This will allow them to be reused
// later on.
PersistentMemoryAllocator::Iterator iter(allocator_.get());
PersistentMemoryAllocator::Reference ref;
ProcessExitCallback process_exit_callback;
{
AutoLock lock(global_tracker_lock_);
process_exit_callback = process_exit_callback_;
}
if (process_exit_callback) {
// Find the processes user-data record so the process phase can be passed
// to the callback.
ActivityUserData::Snapshot process_data_snapshot;
while ((ref = iter.GetNextOfType(kTypeIdProcessDataRecord)) != 0) {
const void* memory = allocator_->GetAsArray<char>(
ref, kTypeIdProcessDataRecord, PersistentMemoryAllocator::kSizeAny);
if (!memory)
continue;
int64_t found_id;
int64_t create_stamp;
if (ActivityUserData::GetOwningProcessId(memory, &found_id,
&create_stamp)) {
if (found_id == process_id && create_stamp < exit_stamp) {
const ActivityUserData process_data(const_cast<void*>(memory),
allocator_->GetAllocSize(ref));
process_data.CreateSnapshot(&process_data_snapshot);
break; // No need to look for any others.
}
}
}
iter.Reset(); // So it starts anew when used below.
// Record the process's phase at exit so callback doesn't need to go
// searching based on a private key value.
ProcessPhase exit_phase = PROCESS_PHASE_UNKNOWN;
auto phase = process_data_snapshot.find(kProcessPhaseDataKey);
if (phase != process_data_snapshot.end())
exit_phase = static_cast<ProcessPhase>(phase->second.GetInt());
// Perform the callback.
process_exit_callback.Run(process_id, exit_stamp, exit_code, exit_phase,
std::move(command_line),
std::move(process_data_snapshot));
}
// Find all allocations associated with the exited process and free them.
uint32_t type;
while ((ref = iter.GetNext(&type)) != 0) {
switch (type) {
case kTypeIdActivityTracker:
case kTypeIdUserDataRecord:
case kTypeIdProcessDataRecord:
case ModuleInfoRecord::kPersistentTypeId: {
const void* memory = allocator_->GetAsArray<char>(
ref, type, PersistentMemoryAllocator::kSizeAny);
if (!memory)
continue;
int64_t found_id;
int64_t create_stamp;
// By convention, the OwningProcess structure is always the first
// field of the structure so there's no need to handle all the
// cases separately.
if (OwningProcess::GetOwningProcessId(memory, &found_id,
&create_stamp)) {
// Only change the type to be "free" if the process ID matches and
// the creation time is before the exit time (so PID re-use doesn't
// cause the erasure of something that is in-use). Memory is cleared
// here, rather than when it's needed, so as to limit the impact at
// that critical time.
if (found_id == process_id && create_stamp < exit_stamp)
allocator_->ChangeType(ref, ~type, type, /*clear=*/true);
}
} break;
}
}
}
void GlobalActivityTracker::RecordLogMessage(StringPiece message) {
// Allocate at least one extra byte so the string is NUL terminated. All
// memory returned by the allocator is guaranteed to be zeroed.
PersistentMemoryAllocator::Reference ref =
allocator_->Allocate(message.size() + 1, kTypeIdGlobalLogMessage);
char* memory = allocator_->GetAsArray<char>(ref, kTypeIdGlobalLogMessage,
message.size() + 1);
if (memory) {
memcpy(memory, message.data(), message.size());
allocator_->MakeIterable(ref);
}
}
void GlobalActivityTracker::RecordModuleInfo(const ModuleInfo& info) {
AutoLock lock(modules_lock_);
auto found = modules_.find(info.file);
if (found != modules_.end()) {
ModuleInfoRecord* record = found->second;
DCHECK(record);
// Update the basic state of module information that has been already
// recorded. It is assumed that the string information (identifier,
// version, etc.) remain unchanged which means that there's no need
// to create a new record to accommodate a possibly longer length.
record->UpdateFrom(info);
return;
}
ModuleInfoRecord* record =
ModuleInfoRecord::CreateFrom(info, allocator_.get());
if (!record)
return;
allocator_->MakeIterable(record);
modules_.emplace(info.file, record);
}
void GlobalActivityTracker::RecordFieldTrial(const std::string& trial_name,
StringPiece group_name) {
const std::string key = std::string("FieldTrial.") + trial_name;
process_data_.SetString(key, group_name);
}
void GlobalActivityTracker::RecordException(const void* pc,
const void* origin,
uint32_t code) {
RecordExceptionImpl(pc, origin, code);
}
void GlobalActivityTracker::MarkDeleted() {
allocator_->SetMemoryState(PersistentMemoryAllocator::MEMORY_DELETED);
}
GlobalActivityTracker::GlobalActivityTracker(
std::unique_ptr<PersistentMemoryAllocator> allocator,
int stack_depth,
int64_t process_id)
: allocator_(std::move(allocator)),
stack_memory_size_(ThreadActivityTracker::SizeForStackDepth(stack_depth)),
process_id_(process_id == 0 ? GetCurrentProcId() : process_id),
this_thread_tracker_(&OnTLSDestroy),
thread_tracker_count_(0),
thread_tracker_allocator_(allocator_.get(),
kTypeIdActivityTracker,
kTypeIdActivityTrackerFree,
stack_memory_size_,
kCachedThreadMemories,
/*make_iterable=*/true),
user_data_allocator_(allocator_.get(),
kTypeIdUserDataRecord,
kTypeIdUserDataRecordFree,
kUserDataSize,
kCachedUserDataMemories,
/*make_iterable=*/true),
process_data_(allocator_->GetAsArray<char>(
AllocateFrom(allocator_.get(),
kTypeIdProcessDataRecordFree,
kProcessDataSize,
kTypeIdProcessDataRecord),
kTypeIdProcessDataRecord,
kProcessDataSize),
kProcessDataSize,
process_id_) {
DCHECK_NE(0, process_id_);
// Ensure that there is no other global object and then make this one such.
DCHECK(!g_tracker_);
subtle::Release_Store(&g_tracker_, reinterpret_cast<uintptr_t>(this));
// The data records must be iterable in order to be found by an analyzer.
allocator_->MakeIterable(allocator_->GetAsReference(
process_data_.GetBaseAddress(), kTypeIdProcessDataRecord));
// Note that this process has launched.
SetProcessPhase(PROCESS_LAUNCHED);
#if !defined(STARBOARD)
// Fetch and record all activated field trials.
FieldTrial::ActiveGroups active_groups;
FieldTrialList::GetActiveFieldTrialGroups(&active_groups);
for (auto& group : active_groups)
RecordFieldTrial(group.trial_name, group.group_name);
#endif
}
GlobalActivityTracker::~GlobalActivityTracker() {
DCHECK(Get() == nullptr || Get() == this);
DCHECK_EQ(0, thread_tracker_count_.load(std::memory_order_relaxed));
subtle::Release_Store(&g_tracker_, 0);
}
void GlobalActivityTracker::ReturnTrackerMemory(
ManagedActivityTracker* tracker) {
PersistentMemoryAllocator::Reference mem_reference = tracker->mem_reference_;
void* mem_base = tracker->mem_base_;
DCHECK(mem_reference);
DCHECK(mem_base);
// Remove the destructed tracker from the set of known ones.
DCHECK_LE(1, thread_tracker_count_.load(std::memory_order_relaxed));
thread_tracker_count_.fetch_sub(1, std::memory_order_relaxed);
// Release this memory for re-use at a later time.
base::AutoLock autolock(thread_tracker_allocator_lock_);
thread_tracker_allocator_.ReleaseObjectReference(mem_reference);
}
void GlobalActivityTracker::RecordExceptionImpl(const void* pc,
const void* origin,
uint32_t code) {
// Get an existing tracker for this thread. It's not possible to create
// one at this point because such would involve memory allocations and
// other potentially complex operations that can cause failures if done
// within an exception handler. In most cases various operations will
// have already created the tracker so this shouldn't generally be a
// problem.
ThreadActivityTracker* tracker = GetTrackerForCurrentThread();
if (!tracker)
return;
tracker->RecordExceptionActivity(pc, origin, Activity::ACT_EXCEPTION,
ActivityData::ForException(code));
}
// static
void GlobalActivityTracker::OnTLSDestroy(void* value) {
delete reinterpret_cast<ManagedActivityTracker*>(value);
}
ScopedActivity::ScopedActivity(const void* program_counter,
uint8_t action,
uint32_t id,
int32_t info)
: GlobalActivityTracker::ScopedThreadActivity(
program_counter,
nullptr,
static_cast<Activity::Type>(Activity::ACT_GENERIC | action),
ActivityData::ForGeneric(id, info),
/*lock_allowed=*/true),
id_(id) {
// The action must not affect the category bits of the activity type.
DCHECK_EQ(0, action & Activity::ACT_CATEGORY_MASK);
}
void ScopedActivity::ChangeAction(uint8_t action) {
DCHECK_EQ(0, action & Activity::ACT_CATEGORY_MASK);
ChangeTypeAndData(static_cast<Activity::Type>(Activity::ACT_GENERIC | action),
kNullActivityData);
}
void ScopedActivity::ChangeInfo(int32_t info) {
ChangeTypeAndData(Activity::ACT_NULL, ActivityData::ForGeneric(id_, info));
}
void ScopedActivity::ChangeActionAndInfo(uint8_t action, int32_t info) {
DCHECK_EQ(0, action & Activity::ACT_CATEGORY_MASK);
ChangeTypeAndData(static_cast<Activity::Type>(Activity::ACT_GENERIC | action),
ActivityData::ForGeneric(id_, info));
}
ScopedTaskRunActivity::ScopedTaskRunActivity(
const void* program_counter,
const base::PendingTask& task)
: GlobalActivityTracker::ScopedThreadActivity(
program_counter,
task.posted_from.program_counter(),
Activity::ACT_TASK_RUN,
ActivityData::ForTask(task.sequence_num),
/*lock_allowed=*/true) {}
ScopedLockAcquireActivity::ScopedLockAcquireActivity(
const void* program_counter,
const base::internal::LockImpl* lock)
: GlobalActivityTracker::ScopedThreadActivity(
program_counter,
nullptr,
Activity::ACT_LOCK_ACQUIRE,
ActivityData::ForLock(lock),
/*lock_allowed=*/false) {}
ScopedEventWaitActivity::ScopedEventWaitActivity(
const void* program_counter,
const base::WaitableEvent* event)
: GlobalActivityTracker::ScopedThreadActivity(
program_counter,
nullptr,
Activity::ACT_EVENT_WAIT,
ActivityData::ForEvent(event),
/*lock_allowed=*/true) {}
ScopedThreadJoinActivity::ScopedThreadJoinActivity(
const void* program_counter,
const base::PlatformThreadHandle* thread)
: GlobalActivityTracker::ScopedThreadActivity(
program_counter,
nullptr,
Activity::ACT_THREAD_JOIN,
ActivityData::ForThread(*thread),
/*lock_allowed=*/true) {}
#if !defined(OS_NACL) && !defined(OS_IOS) && !defined(STARBOARD)
ScopedProcessWaitActivity::ScopedProcessWaitActivity(
const void* program_counter,
const base::Process* process)
: GlobalActivityTracker::ScopedThreadActivity(
program_counter,
nullptr,
Activity::ACT_PROCESS_WAIT,
ActivityData::ForProcess(process->Pid()),
/*lock_allowed=*/true) {}
#endif
} // namespace debug
} // namespace base