// 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 <memory> #include "base/bind.h" #include "base/bind_helpers.h" #include "base/files/file.h" #include "base/files/file_util.h" #include "base/files/memory_mapped_file.h" #include "base/files/scoped_temp_dir.h" #include "base/memory/ptr_util.h" #include "base/pending_task.h" #include "base/rand_util.h" #include "base/synchronization/condition_variable.h" #include "base/synchronization/lock.h" #include "base/synchronization/spin_wait.h" #include "base/threading/platform_thread.h" #include "base/threading/simple_thread.h" #include "base/time/time.h" #include "testing/gtest/include/gtest/gtest.h" namespace base { namespace debug { namespace { class TestActivityTracker : public ThreadActivityTracker { public: TestActivityTracker(std::unique_ptr<char[]> memory, size_t mem_size) : ThreadActivityTracker(memset(memory.get(), 0, mem_size), mem_size), mem_segment_(std::move(memory)) {} ~TestActivityTracker() override = default; private: std::unique_ptr<char[]> mem_segment_; }; } // namespace class ActivityTrackerTest : public testing::Test { public: const int kMemorySize = 1 << 20; // 1MiB const int kStackSize = 1 << 10; // 1KiB using ActivityId = ThreadActivityTracker::ActivityId; ActivityTrackerTest() = default; ~ActivityTrackerTest() override { GlobalActivityTracker* global_tracker = GlobalActivityTracker::Get(); if (global_tracker) { global_tracker->ReleaseTrackerForCurrentThreadForTesting(); delete global_tracker; } } std::unique_ptr<ThreadActivityTracker> CreateActivityTracker() { std::unique_ptr<char[]> memory(new char[kStackSize]); return std::make_unique<TestActivityTracker>(std::move(memory), kStackSize); } size_t GetGlobalActiveTrackerCount() { GlobalActivityTracker* global_tracker = GlobalActivityTracker::Get(); if (!global_tracker) return 0; return global_tracker->thread_tracker_count_.load( std::memory_order_relaxed); } size_t GetGlobalInactiveTrackerCount() { GlobalActivityTracker* global_tracker = GlobalActivityTracker::Get(); if (!global_tracker) return 0; AutoLock autolock(global_tracker->thread_tracker_allocator_lock_); return global_tracker->thread_tracker_allocator_.cache_used(); } size_t GetGlobalUserDataMemoryCacheUsed() { return GlobalActivityTracker::Get()->user_data_allocator_.cache_used(); } void HandleProcessExit(int64_t id, int64_t stamp, int code, GlobalActivityTracker::ProcessPhase phase, std::string&& command, ActivityUserData::Snapshot&& data) { exit_id_ = id; exit_stamp_ = stamp; exit_code_ = code; exit_phase_ = phase; exit_command_ = std::move(command); exit_data_ = std::move(data); } int64_t exit_id_ = 0; int64_t exit_stamp_; int exit_code_; GlobalActivityTracker::ProcessPhase exit_phase_; std::string exit_command_; ActivityUserData::Snapshot exit_data_; }; TEST_F(ActivityTrackerTest, UserDataTest) { char buffer[256]; memset(buffer, 0, sizeof(buffer)); ActivityUserData data(buffer, sizeof(buffer)); size_t space = sizeof(buffer) - sizeof(ActivityUserData::MemoryHeader); ASSERT_EQ(space, data.available_); data.SetInt("foo", 1); space -= 24; ASSERT_EQ(space, data.available_); data.SetUint("b", 1U); // Small names fit beside header in a word. space -= 16; ASSERT_EQ(space, data.available_); data.Set("c", buffer, 10); space -= 24; ASSERT_EQ(space, data.available_); data.SetString("dear john", "it's been fun"); space -= 32; ASSERT_EQ(space, data.available_); data.Set("c", buffer, 20); ASSERT_EQ(space, data.available_); data.SetString("dear john", "but we're done together"); ASSERT_EQ(space, data.available_); data.SetString("dear john", "bye"); ASSERT_EQ(space, data.available_); data.SetChar("d", 'x'); space -= 8; ASSERT_EQ(space, data.available_); data.SetBool("ee", true); space -= 16; ASSERT_EQ(space, data.available_); data.SetString("f", ""); space -= 8; ASSERT_EQ(space, data.available_); } TEST_F(ActivityTrackerTest, PushPopTest) { std::unique_ptr<ThreadActivityTracker> tracker = CreateActivityTracker(); ThreadActivityTracker::Snapshot snapshot; ASSERT_TRUE(tracker->CreateSnapshot(&snapshot)); ASSERT_EQ(0U, snapshot.activity_stack_depth); ASSERT_EQ(0U, snapshot.activity_stack.size()); char origin1; ActivityId id1 = tracker->PushActivity(&origin1, Activity::ACT_TASK, ActivityData::ForTask(11)); ASSERT_TRUE(tracker->CreateSnapshot(&snapshot)); ASSERT_EQ(1U, snapshot.activity_stack_depth); ASSERT_EQ(1U, snapshot.activity_stack.size()); EXPECT_NE(0, snapshot.activity_stack[0].time_internal); EXPECT_EQ(Activity::ACT_TASK, snapshot.activity_stack[0].activity_type); EXPECT_EQ(reinterpret_cast<uintptr_t>(&origin1), snapshot.activity_stack[0].origin_address); EXPECT_EQ(11U, snapshot.activity_stack[0].data.task.sequence_id); char origin2; char lock2; ActivityId id2 = tracker->PushActivity(&origin2, Activity::ACT_LOCK, ActivityData::ForLock(&lock2)); ASSERT_TRUE(tracker->CreateSnapshot(&snapshot)); ASSERT_EQ(2U, snapshot.activity_stack_depth); ASSERT_EQ(2U, snapshot.activity_stack.size()); EXPECT_LE(snapshot.activity_stack[0].time_internal, snapshot.activity_stack[1].time_internal); EXPECT_EQ(Activity::ACT_LOCK, snapshot.activity_stack[1].activity_type); EXPECT_EQ(reinterpret_cast<uintptr_t>(&origin2), snapshot.activity_stack[1].origin_address); EXPECT_EQ(reinterpret_cast<uintptr_t>(&lock2), snapshot.activity_stack[1].data.lock.lock_address); tracker->PopActivity(id2); ASSERT_TRUE(tracker->CreateSnapshot(&snapshot)); ASSERT_EQ(1U, snapshot.activity_stack_depth); ASSERT_EQ(1U, snapshot.activity_stack.size()); EXPECT_EQ(Activity::ACT_TASK, snapshot.activity_stack[0].activity_type); EXPECT_EQ(reinterpret_cast<uintptr_t>(&origin1), snapshot.activity_stack[0].origin_address); EXPECT_EQ(11U, snapshot.activity_stack[0].data.task.sequence_id); tracker->PopActivity(id1); ASSERT_TRUE(tracker->CreateSnapshot(&snapshot)); ASSERT_EQ(0U, snapshot.activity_stack_depth); ASSERT_EQ(0U, snapshot.activity_stack.size()); } TEST_F(ActivityTrackerTest, ScopedTaskTest) { GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0); ThreadActivityTracker* tracker = GlobalActivityTracker::Get()->GetOrCreateTrackerForCurrentThread(); ThreadActivityTracker::Snapshot snapshot; ASSERT_EQ(0U, GetGlobalUserDataMemoryCacheUsed()); ASSERT_TRUE(tracker->CreateSnapshot(&snapshot)); ASSERT_EQ(0U, snapshot.activity_stack_depth); ASSERT_EQ(0U, snapshot.activity_stack.size()); { PendingTask task1(FROM_HERE, DoNothing()); ScopedTaskRunActivity activity1(task1); ActivityUserData& user_data1 = activity1.user_data(); (void)user_data1; // Tell compiler it's been used. ASSERT_TRUE(tracker->CreateSnapshot(&snapshot)); ASSERT_EQ(1U, snapshot.activity_stack_depth); ASSERT_EQ(1U, snapshot.activity_stack.size()); EXPECT_EQ(Activity::ACT_TASK, snapshot.activity_stack[0].activity_type); { PendingTask task2(FROM_HERE, DoNothing()); ScopedTaskRunActivity activity2(task2); ActivityUserData& user_data2 = activity2.user_data(); (void)user_data2; // Tell compiler it's been used. ASSERT_TRUE(tracker->CreateSnapshot(&snapshot)); ASSERT_EQ(2U, snapshot.activity_stack_depth); ASSERT_EQ(2U, snapshot.activity_stack.size()); EXPECT_EQ(Activity::ACT_TASK, snapshot.activity_stack[1].activity_type); } ASSERT_TRUE(tracker->CreateSnapshot(&snapshot)); ASSERT_EQ(1U, snapshot.activity_stack_depth); ASSERT_EQ(1U, snapshot.activity_stack.size()); EXPECT_EQ(Activity::ACT_TASK, snapshot.activity_stack[0].activity_type); } ASSERT_TRUE(tracker->CreateSnapshot(&snapshot)); ASSERT_EQ(0U, snapshot.activity_stack_depth); ASSERT_EQ(0U, snapshot.activity_stack.size()); ASSERT_EQ(2U, GetGlobalUserDataMemoryCacheUsed()); } namespace { class SimpleLockThread : public SimpleThread { public: SimpleLockThread(const std::string& name, Lock* lock) : SimpleThread(name, Options()), lock_(lock), data_changed_(false), is_running_(false) {} ~SimpleLockThread() override = default; void Run() override { ThreadActivityTracker* tracker = GlobalActivityTracker::Get()->GetOrCreateTrackerForCurrentThread(); uint32_t pre_version = tracker->GetDataVersionForTesting(); is_running_.store(true, std::memory_order_relaxed); lock_->Acquire(); data_changed_ = tracker->GetDataVersionForTesting() != pre_version; lock_->Release(); is_running_.store(false, std::memory_order_relaxed); } bool IsRunning() { return is_running_.load(std::memory_order_relaxed); } bool WasDataChanged() { return data_changed_; }; private: Lock* lock_; bool data_changed_; std::atomic<bool> is_running_; DISALLOW_COPY_AND_ASSIGN(SimpleLockThread); }; } // namespace TEST_F(ActivityTrackerTest, LockTest) { GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0); ThreadActivityTracker* tracker = GlobalActivityTracker::Get()->GetOrCreateTrackerForCurrentThread(); ThreadActivityTracker::Snapshot snapshot; ASSERT_EQ(0U, GetGlobalUserDataMemoryCacheUsed()); Lock lock; uint32_t pre_version = tracker->GetDataVersionForTesting(); // Check no activity when only "trying" a lock. EXPECT_TRUE(lock.Try()); EXPECT_EQ(pre_version, tracker->GetDataVersionForTesting()); lock.Release(); EXPECT_EQ(pre_version, tracker->GetDataVersionForTesting()); // Check no activity when acquiring a free lock. SimpleLockThread t1("locker1", &lock); t1.Start(); t1.Join(); EXPECT_FALSE(t1.WasDataChanged()); // Check that activity is recorded when acquring a busy lock. SimpleLockThread t2("locker2", &lock); lock.Acquire(); t2.Start(); while (!t2.IsRunning()) PlatformThread::Sleep(TimeDelta::FromMilliseconds(10)); // t2 can't join until the lock is released but have to give time for t2 to // actually block on the lock before releasing it or the results will not // be correct. PlatformThread::Sleep(TimeDelta::FromMilliseconds(200)); lock.Release(); // Now the results will be valid. t2.Join(); EXPECT_TRUE(t2.WasDataChanged()); } TEST_F(ActivityTrackerTest, ExceptionTest) { GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0); GlobalActivityTracker* global = GlobalActivityTracker::Get(); ThreadActivityTracker* tracker = GlobalActivityTracker::Get()->GetOrCreateTrackerForCurrentThread(); ThreadActivityTracker::Snapshot snapshot; ASSERT_EQ(0U, GetGlobalUserDataMemoryCacheUsed()); ASSERT_TRUE(tracker->CreateSnapshot(&snapshot)); ASSERT_EQ(0U, snapshot.last_exception.activity_type); char origin; global->RecordException(&origin, 42); ASSERT_TRUE(tracker->CreateSnapshot(&snapshot)); EXPECT_EQ(Activity::ACT_EXCEPTION, snapshot.last_exception.activity_type); EXPECT_EQ(reinterpret_cast<uintptr_t>(&origin), snapshot.last_exception.origin_address); EXPECT_EQ(42U, snapshot.last_exception.data.exception.code); } TEST_F(ActivityTrackerTest, CreateWithFileTest) { const char temp_name[] = "CreateWithFileTest"; ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); FilePath temp_file = temp_dir.GetPath().AppendASCII(temp_name); const size_t temp_size = 64 << 10; // 64 KiB // Create a global tracker on a new file. ASSERT_FALSE(PathExists(temp_file)); GlobalActivityTracker::CreateWithFile(temp_file, temp_size, 0, "foo", 3); GlobalActivityTracker* global = GlobalActivityTracker::Get(); EXPECT_EQ(std::string("foo"), global->allocator()->Name()); global->ReleaseTrackerForCurrentThreadForTesting(); delete global; // Create a global tracker over an existing file, replacing it. If the // replacement doesn't work, the name will remain as it was first created. ASSERT_TRUE(PathExists(temp_file)); GlobalActivityTracker::CreateWithFile(temp_file, temp_size, 0, "bar", 3); global = GlobalActivityTracker::Get(); EXPECT_EQ(std::string("bar"), global->allocator()->Name()); global->ReleaseTrackerForCurrentThreadForTesting(); delete global; } // GlobalActivityTracker tests below. TEST_F(ActivityTrackerTest, BasicTest) { GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0); GlobalActivityTracker* global = GlobalActivityTracker::Get(); // Ensure the data repositories have backing store, indicated by non-zero ID. EXPECT_NE(0U, global->process_data().id()); } namespace { class SimpleActivityThread : public SimpleThread { public: SimpleActivityThread(const std::string& name, const void* origin, Activity::Type activity, const ActivityData& data) : SimpleThread(name, Options()), origin_(origin), activity_(activity), data_(data), ready_(false), exit_(false), exit_condition_(&lock_) {} ~SimpleActivityThread() override = default; void Run() override { ThreadActivityTracker::ActivityId id = GlobalActivityTracker::Get() ->GetOrCreateTrackerForCurrentThread() ->PushActivity(origin_, activity_, data_); { AutoLock auto_lock(lock_); ready_.store(true, std::memory_order_release); while (!exit_.load(std::memory_order_relaxed)) exit_condition_.Wait(); } GlobalActivityTracker::Get()->GetTrackerForCurrentThread()->PopActivity(id); } void Exit() { AutoLock auto_lock(lock_); exit_.store(true, std::memory_order_relaxed); exit_condition_.Signal(); } void WaitReady() { SPIN_FOR_1_SECOND_OR_UNTIL_TRUE(ready_.load(std::memory_order_acquire)); } private: const void* origin_; Activity::Type activity_; ActivityData data_; std::atomic<bool> ready_; std::atomic<bool> exit_; Lock lock_; ConditionVariable exit_condition_; DISALLOW_COPY_AND_ASSIGN(SimpleActivityThread); }; } // namespace TEST_F(ActivityTrackerTest, ThreadDeathTest) { GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0); GlobalActivityTracker::Get()->GetOrCreateTrackerForCurrentThread(); const size_t starting_active = GetGlobalActiveTrackerCount(); const size_t starting_inactive = GetGlobalInactiveTrackerCount(); SimpleActivityThread t1("t1", nullptr, Activity::ACT_TASK, ActivityData::ForTask(11)); t1.Start(); t1.WaitReady(); EXPECT_EQ(starting_active + 1, GetGlobalActiveTrackerCount()); EXPECT_EQ(starting_inactive, GetGlobalInactiveTrackerCount()); t1.Exit(); t1.Join(); EXPECT_EQ(starting_active, GetGlobalActiveTrackerCount()); EXPECT_EQ(starting_inactive + 1, GetGlobalInactiveTrackerCount()); // Start another thread and ensure it re-uses the existing memory. SimpleActivityThread t2("t2", nullptr, Activity::ACT_TASK, ActivityData::ForTask(22)); t2.Start(); t2.WaitReady(); EXPECT_EQ(starting_active + 1, GetGlobalActiveTrackerCount()); EXPECT_EQ(starting_inactive, GetGlobalInactiveTrackerCount()); t2.Exit(); t2.Join(); EXPECT_EQ(starting_active, GetGlobalActiveTrackerCount()); EXPECT_EQ(starting_inactive + 1, GetGlobalInactiveTrackerCount()); } TEST_F(ActivityTrackerTest, ProcessDeathTest) { // This doesn't actually create and destroy a process. Instead, it uses for- // testing interfaces to simulate data created by other processes. const int64_t other_process_id = GetCurrentProcId() + 1; GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0); GlobalActivityTracker* global = GlobalActivityTracker::Get(); ThreadActivityTracker* thread = global->GetOrCreateTrackerForCurrentThread(); // Get callbacks for process exit. global->SetProcessExitCallback( Bind(&ActivityTrackerTest::HandleProcessExit, Unretained(this))); // Pretend than another process has started. global->RecordProcessLaunch(other_process_id, FILE_PATH_LITERAL("foo --bar")); // Do some activities. PendingTask task(FROM_HERE, DoNothing()); ScopedTaskRunActivity activity(task); ActivityUserData& user_data = activity.user_data(); ASSERT_NE(0U, user_data.id()); // Get the memory-allocator references to that data. PersistentMemoryAllocator::Reference proc_data_ref = global->allocator()->GetAsReference( global->process_data().GetBaseAddress(), GlobalActivityTracker::kTypeIdProcessDataRecord); ASSERT_TRUE(proc_data_ref); PersistentMemoryAllocator::Reference tracker_ref = global->allocator()->GetAsReference( thread->GetBaseAddress(), GlobalActivityTracker::kTypeIdActivityTracker); ASSERT_TRUE(tracker_ref); PersistentMemoryAllocator::Reference user_data_ref = global->allocator()->GetAsReference( user_data.GetBaseAddress(), GlobalActivityTracker::kTypeIdUserDataRecord); ASSERT_TRUE(user_data_ref); // Make a copy of the thread-tracker state so it can be restored later. const size_t tracker_size = global->allocator()->GetAllocSize(tracker_ref); std::unique_ptr<char[]> tracker_copy(new char[tracker_size]); memcpy(tracker_copy.get(), thread->GetBaseAddress(), tracker_size); // Change the objects to appear to be owned by another process. Use a "past" // time so that exit-time is always later than create-time. const int64_t past_stamp = Time::Now().ToInternalValue() - 1; int64_t owning_id; int64_t stamp; ASSERT_TRUE(ActivityUserData::GetOwningProcessId( global->process_data().GetBaseAddress(), &owning_id, &stamp)); EXPECT_NE(other_process_id, owning_id); ASSERT_TRUE(ThreadActivityTracker::GetOwningProcessId( thread->GetBaseAddress(), &owning_id, &stamp)); EXPECT_NE(other_process_id, owning_id); ASSERT_TRUE(ActivityUserData::GetOwningProcessId(user_data.GetBaseAddress(), &owning_id, &stamp)); EXPECT_NE(other_process_id, owning_id); global->process_data().SetOwningProcessIdForTesting(other_process_id, past_stamp); thread->SetOwningProcessIdForTesting(other_process_id, past_stamp); user_data.SetOwningProcessIdForTesting(other_process_id, past_stamp); ASSERT_TRUE(ActivityUserData::GetOwningProcessId( global->process_data().GetBaseAddress(), &owning_id, &stamp)); EXPECT_EQ(other_process_id, owning_id); ASSERT_TRUE(ThreadActivityTracker::GetOwningProcessId( thread->GetBaseAddress(), &owning_id, &stamp)); EXPECT_EQ(other_process_id, owning_id); ASSERT_TRUE(ActivityUserData::GetOwningProcessId(user_data.GetBaseAddress(), &owning_id, &stamp)); EXPECT_EQ(other_process_id, owning_id); // Check that process exit will perform callback and free the allocations. ASSERT_EQ(0, exit_id_); ASSERT_EQ(GlobalActivityTracker::kTypeIdProcessDataRecord, global->allocator()->GetType(proc_data_ref)); ASSERT_EQ(GlobalActivityTracker::kTypeIdActivityTracker, global->allocator()->GetType(tracker_ref)); ASSERT_EQ(GlobalActivityTracker::kTypeIdUserDataRecord, global->allocator()->GetType(user_data_ref)); global->RecordProcessExit(other_process_id, 0); EXPECT_EQ(other_process_id, exit_id_); EXPECT_EQ("foo --bar", exit_command_); EXPECT_EQ(GlobalActivityTracker::kTypeIdProcessDataRecordFree, global->allocator()->GetType(proc_data_ref)); EXPECT_EQ(GlobalActivityTracker::kTypeIdActivityTrackerFree, global->allocator()->GetType(tracker_ref)); EXPECT_EQ(GlobalActivityTracker::kTypeIdUserDataRecordFree, global->allocator()->GetType(user_data_ref)); // Restore memory contents and types so things don't crash when doing real // process clean-up. memcpy(const_cast<void*>(thread->GetBaseAddress()), tracker_copy.get(), tracker_size); global->allocator()->ChangeType( proc_data_ref, GlobalActivityTracker::kTypeIdProcessDataRecord, GlobalActivityTracker::kTypeIdUserDataRecordFree, false); global->allocator()->ChangeType( tracker_ref, GlobalActivityTracker::kTypeIdActivityTracker, GlobalActivityTracker::kTypeIdActivityTrackerFree, false); global->allocator()->ChangeType( user_data_ref, GlobalActivityTracker::kTypeIdUserDataRecord, GlobalActivityTracker::kTypeIdUserDataRecordFree, false); } } // namespace debug } // namespace base