|  | // 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 "starboard/memory.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 |