Index: base/debug/activity_tracker.cc |
diff --git a/base/debug/activity_tracker.cc b/base/debug/activity_tracker.cc |
index 444dc7067eb5a47a82cf7f353317ed6ecbd4b29a..fcd17fea9cf76ef0d9d76fc6f769c6f4c9165b62 100644 |
--- a/base/debug/activity_tracker.cc |
+++ b/base/debug/activity_tracker.cc |
@@ -104,6 +104,11 @@ 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 |
OwningProcess::OwningProcess() {} |
@@ -317,6 +322,8 @@ ActivityUserData::MemoryHeader::~MemoryHeader() {} |
ActivityUserData::FieldHeader::FieldHeader() {} |
ActivityUserData::FieldHeader::~FieldHeader() {} |
+ActivityUserData::ActivityUserData() : ActivityUserData(nullptr, 0) {} |
+ |
ActivityUserData::ActivityUserData(void* memory, size_t size) |
: memory_(reinterpret_cast<char*>(memory)), |
available_(RoundDownToAlignment(size, kMemoryAlignment)), |
@@ -560,7 +567,8 @@ struct ThreadActivityTracker::Header { |
// Expected size for 32/64-bit check. |
static constexpr size_t kExpectedInstanceSize = |
- OwningProcess::kExpectedInstanceSize + 72; |
+ OwningProcess::kExpectedInstanceSize + Activity::kExpectedInstanceSize + |
+ 72; |
// This information uniquely identifies a process. |
OwningProcess owner; |
@@ -588,7 +596,7 @@ struct ThreadActivityTracker::Header { |
// won't be recorded. |
std::atomic<uint32_t> current_depth; |
- // A memory location used to indicate if changes have been made to the stack |
+ // 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 zero the value whenever something gets popped from the |
// stack. A monitoring tracker can write a non-zero value here, copy the |
@@ -596,7 +604,11 @@ struct ThreadActivityTracker::Header { |
// the contents didn't change while being copied. This can handle concurrent |
// snapshot operations only if each snapshot writes a different bit (which |
// is not the current implementation so no parallel snapshots allowed). |
- std::atomic<uint32_t> stack_unchanged; |
+ std::atomic<uint32_t> data_unchanged; |
+ |
+ // 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 |
@@ -674,7 +686,7 @@ ThreadActivityTracker::ThreadActivityTracker(void* base, size_t size) |
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_->stack_unchanged.load(std::memory_order_relaxed)); |
+ DCHECK_EQ(0U, header_->data_unchanged.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]); |
@@ -788,40 +800,28 @@ void ThreadActivityTracker::PopActivity(ActivityId id) { |
// The stack has shrunk meaning that some other thread trying to copy the |
// contents for reporting purposes could get bad data. That thread would |
- // have written a non-zero value into |stack_unchanged|; clearing it here |
+ // have written a non-zero value into |data_unchanged|; clearing it here |
// will let that thread detect that something did change. This needs to |
// happen after the atomic |depth| operation above so a "release" store |
// is required. |
- header_->stack_unchanged.store(0, std::memory_order_release); |
+ header_->data_unchanged.store(0, std::memory_order_release); |
} |
std::unique_ptr<ActivityUserData> ThreadActivityTracker::GetUserData( |
ActivityId id, |
ActivityTrackerMemoryAllocator* allocator) { |
- // User-data is only stored for activities actually held in the stack. |
- if (id < stack_slots_) { |
- // Don't allow user data for lock acquisition as recursion may occur. |
- if (stack_[id].activity_type == Activity::ACT_LOCK_ACQUIRE) { |
- NOTREACHED(); |
- return MakeUnique<ActivityUserData>(nullptr, 0); |
- } |
- |
- // Get (or reuse) a block of memory and create a real UserData object |
- // on it. |
- PersistentMemoryAllocator::Reference ref = allocator->GetObjectReference(); |
- void* memory = |
- allocator->GetAsArray<char>(ref, PersistentMemoryAllocator::kSizeAny); |
- if (memory) { |
- std::unique_ptr<ActivityUserData> user_data = |
- MakeUnique<ActivityUserData>(memory, kUserDataSize); |
- stack_[id].user_data_ref = ref; |
- stack_[id].user_data_id = user_data->id(); |
- return user_data; |
- } |
+ // Don't allow user data for lock acquisition as recursion may occur. |
+ if (stack_[id].activity_type == Activity::ACT_LOCK_ACQUIRE) { |
+ NOTREACHED(); |
+ return MakeUnique<ActivityUserData>(); |
} |
- // Return a dummy object that will still accept (but ignore) Set() calls. |
- return MakeUnique<ActivityUserData>(nullptr, 0); |
+ // User-data is only stored for activities actually held in the stack. |
+ if (id >= stack_slots_) |
+ return MakeUnique<ActivityUserData>(); |
+ |
+ // Create and return a real UserData object. |
+ return CreateUserDataForActivity(&stack_[id], allocator); |
} |
bool ThreadActivityTracker::HasUserData(ActivityId id) { |
@@ -839,6 +839,23 @@ void ThreadActivityTracker::ReleaseUserData( |
} |
} |
+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(thread_checker_.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_unchanged.store(0, 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 || |
@@ -881,12 +898,12 @@ bool ThreadActivityTracker::CreateSnapshot(Snapshot* output_snapshot) const { |
const int64_t starting_process_id = header_->owner.process_id; |
const int64_t starting_thread_id = header_->thread_ref.as_id; |
- // Write a non-zero value to |stack_unchanged| so it's possible to detect |
+ // Write a non-zero value to |data_unchanged| 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. |
- header_->stack_unchanged.store(1, std::memory_order_seq_cst); |
+ header_->data_unchanged.store(1, 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); |
@@ -898,16 +915,20 @@ bool ThreadActivityTracker::CreateSnapshot(Snapshot* output_snapshot) const { |
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_->stack_unchanged.load(std::memory_order_seq_cst)) |
+ if (!header_->data_unchanged.load(std::memory_order_seq_cst)) |
continue; |
// Stack copied. Record it's full depth. |
output_snapshot->activity_stack_depth = depth; |
- // TODO(bcwhite): Snapshot other things here. |
- |
// Get the general thread information. |
output_snapshot->thread_name = |
std::string(header_->thread_name, sizeof(header_->thread_name) - 1); |
@@ -939,10 +960,14 @@ bool ThreadActivityTracker::CreateSnapshot(Snapshot* output_snapshot) const { |
const int64_t start_ticks = header_->start_ticks; |
for (Activity& activity : output_snapshot->activity_stack) { |
activity.time_internal = |
- (start_time + |
- TimeDelta::FromInternalValue(activity.time_internal - start_ticks)) |
+ 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; |
@@ -974,6 +999,26 @@ size_t ThreadActivityTracker::SizeForStackDepth(int stack_depth) { |
return static_cast<size_t>(stack_depth) * sizeof(Activity) + sizeof(Header); |
} |
+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 = |
+ MakeUnique<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 MakeUnique<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 |
@@ -1127,7 +1172,7 @@ ActivityUserData& GlobalActivityTracker::ScopedThreadActivity::user_data() { |
user_data_ = |
tracker_->GetUserData(activity_id_, &global->user_data_allocator_); |
} else { |
- user_data_ = MakeUnique<ActivityUserData>(nullptr, 0); |
+ user_data_ = MakeUnique<ActivityUserData>(); |
} |
} |
return *user_data_; |
@@ -1559,6 +1604,22 @@ void GlobalActivityTracker::ReturnTrackerMemory( |
thread_tracker_allocator_.ReleaseObjectReference(mem_reference); |
} |
+void GlobalActivityTracker::RecordExceptionImpl(const void* pc, |
+ const void* origin) { |
+ // 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::ForGeneric(0, 0)); |
+} |
+ |
// static |
void GlobalActivityTracker::OnTLSDestroy(void* value) { |
delete reinterpret_cast<ManagedActivityTracker*>(value); |