910e62b5创建于 1月15日历史提交
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/threading/platform_thread_metrics.h"

#include <array>
#include <atomic>
#include <optional>
#include <string>
#include <string_view>
#include <vector>

#include "base/metrics/histogram_base.h"
#include "base/metrics/statistics_recorder.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/task/current_thread.h"
#include "base/task/thread_pool.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/test/test_timeouts.h"
#include "base/test/test_waitable_event.h"
#include "base/threading/platform_thread.h"
#include "base/threading/simple_thread.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "build/build_config.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

#if BUILDFLAG(IS_ANDROID)
#include <linux/resource.h>
#include <sys/resource.h>
#endif  // BUILDFLAG(IS_ANDROID)

#if BUILDFLAG(IS_WIN)
#include <windows.h>
#endif

namespace base {

namespace {

using ::testing::Ge;
using ::testing::Gt;
using ::testing::Optional;

void BusyWork() {
  ElapsedTimer timer;
  while (timer.Elapsed() < TestTimeouts::tiny_timeout()) {
    std::vector<std::string> vec;
    int64_t test_value = 0;
    for (int i = 0; i < 100000; ++i) {
      ++test_value;
      vec.push_back(NumberToString(test_value));
    }
  }
}

class MetricsTestThread final : public SimpleThread {
 public:
  MetricsTestThread() : SimpleThread("MetricsTestThread") {}

  MetricsTestThread(const MetricsTestThread&) = delete;
  MetricsTestThread& operator=(const MetricsTestThread&) = delete;

  ~MetricsTestThread() final {
    if (HasBeenStarted() && !HasBeenJoined()) {
      Stop();
    }
  }

  PlatformThreadHandle handle() const {
    handle_ready_event_.Wait();
    return handle_.load(std::memory_order_relaxed);
  }

  // Stop the thread.
  void Stop() {
    ASSERT_TRUE(HasBeenStarted());
    ASSERT_FALSE(HasBeenJoined());
    stop_event_.Signal();
    Join();
  }

  // Cause the thread to do busy work. The caller will block until it's done.
  void DoBusyWork() {
    ASSERT_TRUE(HasBeenStarted());
    ASSERT_FALSE(HasBeenJoined());
    do_busy_work_event_.Signal();
    done_busy_work_event_.Wait();
  }

  // SimpleThread:
  void Run() final {
#if BUILDFLAG(IS_WIN)
    // CurrentHandle() returns a pseudo-handle that's the same in every thread.
    // Duplicate it to get a real handle.
    HANDLE win_handle;
    ASSERT_TRUE(::DuplicateHandle(::GetCurrentProcess(), ::GetCurrentThread(),
                                  ::GetCurrentProcess(), &win_handle,
                                  THREAD_QUERY_LIMITED_INFORMATION, FALSE, 0));
    PlatformThreadHandle handle(win_handle);
#else
    PlatformThreadHandle handle = PlatformThread::CurrentHandle();
#endif
    handle_.store(handle, std::memory_order_relaxed);
    handle_ready_event_.Signal();

    std::array<WaitableEvent*, 2> events{&do_busy_work_event_, &stop_event_};
    while (!stop_event_.IsSignaled()) {
      // WaitMany returns the lowest index among signaled events.
      if (WaitableEvent::WaitMany(events) == 0) {
        // DoBusyWork() waits on `done_busy_work_event_` so it should be
        // impossible to signal `stop_event_` too.
        ASSERT_FALSE(stop_event_.IsSignaled());
        BusyWork();
        done_busy_work_event_.Signal();
      }
    }
  }

 private:
  // When this is signaled, stop the thread.
  TestWaitableEvent stop_event_;

  // When `do_busy_work_event_` is signaled, do some busy work, then signal
  // `done_busy_work_event_`. These are auto-reset so they can be reused to do
  // more busy work.
  TestWaitableEvent do_busy_work_event_{WaitableEvent::ResetPolicy::AUTOMATIC};
  TestWaitableEvent done_busy_work_event_{
      WaitableEvent::ResetPolicy::AUTOMATIC};

  std::atomic<PlatformThreadHandle> handle_;
  mutable TestWaitableEvent handle_ready_event_;
};

class PlatformThreadMetricsTest : public ::testing::Test {
 public:
  void SetUp() override {
#if BUILDFLAG(IS_WIN) && !defined(ARCH_CPU_ARM64)
    // TSC is only initialized once TSCTicksPerSecond() is called twice at least
    // 50 ms apart on the same thread to get a baseline. If the system has a
    // TSC, make sure it's initialized so all GetCumulativeCPUUsage calls use
    // it.
    if (time_internal::HasConstantRateTSC()) {
      if (time_internal::TSCTicksPerSecond() == 0) {
        PlatformThread::Sleep(Milliseconds(51));
      }
      ASSERT_GT(time_internal::TSCTicksPerSecond(), 0);
    }
#endif
  }
};

}  // namespace

#if BUILDFLAG(IS_APPLE) || BUILDFLAG(IS_WIN)
TEST_F(PlatformThreadMetricsTest, CreateFromHandle) {
  EXPECT_FALSE(PlatformThreadMetrics::CreateFromHandle(PlatformThreadHandle()));
  EXPECT_TRUE(
      PlatformThreadMetrics::CreateFromHandle(PlatformThread::CurrentHandle()));

  MetricsTestThread thread;
  thread.Start();
  PlatformThreadHandle handle = thread.handle();
  ASSERT_FALSE(handle.is_null());
  EXPECT_FALSE(handle.is_equal(PlatformThread::CurrentHandle()));
  EXPECT_TRUE(PlatformThreadMetrics::CreateFromHandle(handle));
}
#endif

#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_CHROMEOS) || \
    BUILDFLAG(IS_FUCHSIA) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN)
TEST_F(PlatformThreadMetricsTest, CreateFromId) {
  EXPECT_FALSE(PlatformThreadMetrics::CreateFromId(PlatformThreadId()));
  EXPECT_FALSE(PlatformThreadMetrics::CreateFromId(kInvalidThreadId));
  EXPECT_TRUE(PlatformThreadMetrics::CreateFromId(PlatformThread::CurrentId()));

  MetricsTestThread thread;
  thread.Start();
  PlatformThreadId tid = thread.tid();
  ASSERT_NE(tid, kInvalidThreadId);
  EXPECT_NE(tid, PlatformThread::CurrentId());
  EXPECT_TRUE(PlatformThreadMetrics::CreateFromId(tid));
}
#endif

TEST_F(PlatformThreadMetricsTest, GetCumulativeCPUUsage_CurrentThread) {
  auto metrics = PlatformThreadMetrics::CreateForCurrentThread();
  ASSERT_TRUE(metrics);
  const auto cpu_usage = metrics->GetCumulativeCPUUsage();
  ASSERT_THAT(cpu_usage, Optional(Ge(TimeDelta())));

  // First call to GetCPUUsageProportion() always returns 0.
  EXPECT_EQ(metrics->GetCPUUsageProportion(cpu_usage.value()), 0.0);

  BusyWork();

  const auto cpu_usage2 = metrics->GetCumulativeCPUUsage();
  ASSERT_THAT(cpu_usage2, Optional(Gt(cpu_usage.value())));

  // Should be capped at 100%, but may be higher due to rounding so there's no
  // strict upper bound to test.
  EXPECT_GT(metrics->GetCPUUsageProportion(cpu_usage2.value()), 0.0);
}

TEST_F(PlatformThreadMetricsTest, GetCumulativeCPUUsage_OtherThread) {
  MetricsTestThread thread;
  thread.Start();
#if BUILDFLAG(IS_APPLE)
  // Apple is the only platform that doesn't support CreateFromId().
  ASSERT_FALSE(thread.handle().is_null());
  auto metrics = PlatformThreadMetrics::CreateFromHandle(thread.handle());
#else
  ASSERT_NE(thread.tid(), kInvalidThreadId);
  auto metrics = PlatformThreadMetrics::CreateFromId(thread.tid());
#endif
  ASSERT_TRUE(metrics);

  const auto cpu_usage = metrics->GetCumulativeCPUUsage();
  ASSERT_THAT(cpu_usage, Optional(Ge(TimeDelta())));

  // First call to GetCPUUsageProportion() always returns 0.
  EXPECT_EQ(metrics->GetCPUUsageProportion(cpu_usage.value()), 0.0);

  thread.DoBusyWork();

  const auto cpu_usage2 = metrics->GetCumulativeCPUUsage();
  ASSERT_THAT(cpu_usage2, Optional(Gt(cpu_usage.value())));

  // Should be capped at 100%, but may be higher due to rounding so there's no
  // strict upper bound to test.
  EXPECT_GT(metrics->GetCPUUsageProportion(cpu_usage2.value()), 0.0);

  thread.Stop();

  // Thread is no longer running.
  const auto cpu_usage3 = metrics->GetCumulativeCPUUsage();

  // Ensure that measuring the CPU usage of a stopped thread doesn't give bogus
  // values, although it may fail on some platforms. (If the measurement works,
  // it will include any CPU used between the last measurement and the join.)

#if BUILDFLAG(IS_WIN)
  // Windows can always read the final CPU usage of a stopped thread.
  ASSERT_NE(cpu_usage3, std::nullopt);
#else
  // POSIX platforms are racy, so the measurement may fail. Apple and Fuchsia
  // seem to always fail, but if a change causes measurements to start working,
  // that's good too.
  if (cpu_usage3.has_value()) {
    EXPECT_GE(cpu_usage3.value(), cpu_usage2.value());
  }
#endif
}

#if BUILDFLAG(IS_ANDROID)
class PlatformThreadPriorityMonitorTest : public ::testing::Test {
 public:
  static constexpr std::string_view kMainThreadSuffix = "MainThread";
  static constexpr std::string_view kChildThreadSuffix = "ChildThread";
  static constexpr TimeDelta kMinSamplingInterval =
      PlatformThreadPriorityMonitor::kMinSamplingInterval;

  void TearDown() override { ASSERT_EQ(setpriority(PRIO_PROCESS, 0, 0), 0); }

  size_t GetRegisteredThreadCount() {
    auto& monitor = PlatformThreadPriorityMonitor::Get();
    AutoLock lock(monitor.lock_);
    return monitor.thread_id_to_histogram_.size();
  }

  static std::string HistogramNameForSuffix(const std::string_view& suffix) {
    return PlatformThreadPriorityMonitor::Get().GetHistogramNameForSuffix(
        suffix);
  }
};

class PriorityMonitorTestDelegate : public PlatformThread::Delegate {
 public:
  PriorityMonitorTestDelegate() = default;
  ~PriorityMonitorTestDelegate() override = default;

  void ThreadMain() override {
    PlatformThreadPriorityMonitor::Get().RegisterCurrentThread(
        PlatformThreadPriorityMonitorTest::kChildThreadSuffix);
    registered_event_.Signal();
    stop_event_.Wait();
  }

  void WaitUntilRegistered() { registered_event_.Wait(); }

  void SignalStop() { stop_event_.Signal(); }

 private:
  TestWaitableEvent stop_event_;
  TestWaitableEvent registered_event_;
};

// Test UnregisterCurrentThread() is called on thread exit.
TEST_F(PlatformThreadPriorityMonitorTest, UnregisterOnJoin) {
  ASSERT_EQ(0u, GetRegisteredThreadCount());

  PriorityMonitorTestDelegate delegate;
  PlatformThreadHandle handle;
  ASSERT_TRUE(PlatformThread::Create(0, &delegate, &handle));

  delegate.WaitUntilRegistered();
  EXPECT_EQ(1u, GetRegisteredThreadCount());

  delegate.SignalStop();
  PlatformThread::Join(handle);

  EXPECT_EQ(0u, GetRegisteredThreadCount());
}

// Test that priority monitor reports thread priorities for all registered
// threads.
TEST_F(PlatformThreadPriorityMonitorTest, ReportThreadPriorities) {
  test::TaskEnvironment task_environment{
      test::TaskEnvironment::TimeSource::MOCK_TIME};

  PriorityMonitorTestDelegate delegate;
  PlatformThreadHandle handle;
  ASSERT_TRUE(PlatformThread::Create(0, &delegate, &handle));

  // Register the current thread and start monitoring thread priorities.
  PlatformThreadPriorityMonitor::Get().RegisterCurrentThread(kMainThreadSuffix);
  PlatformThreadPriorityMonitor::Get().Start();

  // Set the priority of the current thread to a different value than the child
  // thread.
  constexpr int kTestNiceValue = -7;
  ASSERT_EQ(setpriority(PRIO_PROCESS, 0, kTestNiceValue), 0);

  delegate.WaitUntilRegistered();
  EXPECT_EQ(2u, GetRegisteredThreadCount());

  HistogramTester histogram_tester;
  const std::string main_thread_histogram_name =
      HistogramNameForSuffix(kMainThreadSuffix);
  const std::string child_thread_histogram_name =
      HistogramNameForSuffix(kChildThreadSuffix);

  task_environment.FastForwardBy(Milliseconds(1));
  histogram_tester.ExpectTotalCount(main_thread_histogram_name, 0);
  histogram_tester.ExpectTotalCount(child_thread_histogram_name, 0);

  // Should record a sample for each thread.
  task_environment.FastForwardBy(kMinSamplingInterval - Milliseconds(1));
  histogram_tester.ExpectTotalCount(main_thread_histogram_name, 1);
  histogram_tester.ExpectUniqueSample(main_thread_histogram_name,
                                      kTestNiceValue, 1);
  histogram_tester.ExpectTotalCount(child_thread_histogram_name, 1);
  histogram_tester.ExpectUniqueSample(child_thread_histogram_name, 0, 1);

  delegate.SignalStop();
  PlatformThread::Join(handle);
}
#endif  // BUILDFLAG(IS_ANDROID)

}  // namespace base