| // 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 |