From 4dfe9e761b7aac59e9a9724654f305e09e9e9a80 Mon Sep 17 00:00:00 2001 From: esmael Date: Wed, 29 May 2024 15:59:11 -0700 Subject: [PATCH] Optimistically read from /proc for process stats. b/341774149 --- base/BUILD.gn | 7 + base/process/internal_linux.cc | 31 ++- base/process/internal_linux.h | 12 + cobalt/base/BUILD.gn | 3 + cobalt/base/process/process_metrics_helper.cc | 178 ++++++++++++++ cobalt/base/process/process_metrics_helper.h | 54 +++++ .../process/process_metrics_helper_test.cc | 223 ++++++++++++++++++ starboard/shared/win32/test_filters.py | 7 + 8 files changed, 505 insertions(+), 10 deletions(-) create mode 100644 cobalt/base/process/process_metrics_helper.cc create mode 100644 cobalt/base/process/process_metrics_helper.h create mode 100644 cobalt/base/process/process_metrics_helper_test.cc diff --git a/base/BUILD.gn b/base/BUILD.gn index 4e7f2a028b15..5ad4e4869019 100644 --- a/base/BUILD.gn +++ b/base/BUILD.gn @@ -1065,6 +1065,13 @@ component("base") { ] } + if (use_cobalt_customizations) { + sources += [ + "process/internal_linux.cc", + "process/internal_linux.h", + ] + } + if (!use_cobalt_customizations && (is_linux || is_chromeos || is_android)) { sources += [ "files/file_path_watcher_inotify.cc", diff --git a/base/process/internal_linux.cc b/base/process/internal_linux.cc index ed2c4416224f..681d020e72b2 100644 --- a/base/process/internal_linux.cc +++ b/base/process/internal_linux.cc @@ -33,6 +33,7 @@ const char kProcDir[] = "/proc"; const char kStatFile[] = "stat"; +#if !defined(STARBOARD) FilePath GetProcPidDir(pid_t pid) { return FilePath(kProcDir).Append(NumberToString(pid)); } @@ -56,6 +57,7 @@ pid_t ProcDirSlotToPid(const char* d_name) { } return pid; } +#endif // !defined(STARBOARD) bool ReadProcFile(const FilePath& file, std::string* buffer) { DCHECK(FilePath(kProcDir).IsParent(file)); @@ -70,10 +72,12 @@ bool ReadProcFile(const FilePath& file, std::string* buffer) { return !buffer->empty(); } +#if !defined(STARBOARD) bool ReadProcStats(pid_t pid, std::string* buffer) { FilePath stat_file = internal::GetProcPidDir(pid).Append(kStatFile); return ReadProcFile(stat_file, buffer); } +#endif // !defined(STARBOARD) bool ParseProcStats(const std::string& stats_data, std::vector* proc_stats) { @@ -86,28 +90,29 @@ bool ParseProcStats(const std::string& stats_data, // pid (process name) data1 data2 .... dataN // Look for the closing paren by scanning backwards, to avoid being fooled by // processes with ')' in the name. - size_t open_parens_idx = stats_data.find(" ("); - size_t close_parens_idx = stats_data.rfind(") "); - if (open_parens_idx == std::string::npos || - close_parens_idx == std::string::npos || - open_parens_idx > close_parens_idx) { + size_t pid_end = stats_data.find(" ("); + size_t comm_start = pid_end + 2; + size_t comm_end = stats_data.rfind(") "); + size_t state_start = comm_end + 2; + if (pid_end == std::string::npos || + comm_end == std::string::npos || + pid_end > comm_end) { DLOG(WARNING) << "Failed to find matched parens in '" << stats_data << "'"; NOTREACHED(); return false; } - open_parens_idx++; proc_stats->clear(); // PID. - proc_stats->push_back(stats_data.substr(0, open_parens_idx)); + proc_stats->push_back(stats_data.substr(0, pid_end)); // Process name without parentheses. proc_stats->push_back( - stats_data.substr(open_parens_idx + 1, - close_parens_idx - (open_parens_idx + 1))); + stats_data.substr(comm_start, + comm_end - comm_start)); // Split the rest. std::vector other_stats = SplitString( - stats_data.substr(close_parens_idx + 2), " ", + stats_data.substr(state_start), " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); for (const auto& i : other_stats) proc_stats->push_back(i); @@ -152,16 +157,19 @@ int64_t ReadStatFileAndGetFieldAsInt64(const FilePath& stat_file, return GetProcStatsFieldAsInt64(proc_stats, field_num); } +#if !defined(STARBOARD) int64_t ReadProcStatsAndGetFieldAsInt64(pid_t pid, ProcStatsFields field_num) { FilePath stat_file = internal::GetProcPidDir(pid).Append(kStatFile); return ReadStatFileAndGetFieldAsInt64(stat_file, field_num); } +#endif // !defined(STARBOARD) int64_t ReadProcSelfStatsAndGetFieldAsInt64(ProcStatsFields field_num) { FilePath stat_file = FilePath(kProcDir).Append("self").Append(kStatFile); return ReadStatFileAndGetFieldAsInt64(stat_file, field_num); } +#if !defined(STARBOARD) size_t ReadProcStatsAndGetFieldAsSizeT(pid_t pid, ProcStatsFields field_num) { std::string stats_data; @@ -172,6 +180,7 @@ size_t ReadProcStatsAndGetFieldAsSizeT(pid_t pid, return 0; return GetProcStatsFieldAsSizeT(proc_stats, field_num); } +#endif // !defined(STARBOARD) Time GetBootTime() { FilePath path("/proc/stat"); @@ -189,6 +198,7 @@ Time GetBootTime() { return Time::FromTimeT(btime); } +#if !defined(STARBOARD) TimeDelta GetUserCpuTimeSinceBoot() { FilePath path("/proc/stat"); std::string contents; @@ -228,6 +238,7 @@ TimeDelta ClockTicksToTimeDelta(int64_t clock_ticks) { return Microseconds(Time::kMicrosecondsPerSecond * clock_ticks / kHertz); } +#endif // !defined(STARBOARD) } // namespace internal } // namespace base diff --git a/base/process/internal_linux.h b/base/process/internal_linux.h index d7cc2f121fe7..f59643f55289 100644 --- a/base/process/internal_linux.h +++ b/base/process/internal_linux.h @@ -14,7 +14,9 @@ #include #include +#if !defined(STARBOARD) #include "base/files/dir_reader_posix.h" +#endif // !defined(STARBOARD) #include "base/files/file_path.h" #include "base/process/process_handle.h" #include "base/strings/string_number_conversions.h" @@ -33,14 +35,17 @@ extern const char kProcDir[]; // "stat" extern const char kStatFile[]; +#if !defined(STARBOARD) // Returns a FilePath to "/proc/pid". base::FilePath GetProcPidDir(pid_t pid); +#endif // !defined(STARBOARD) // Reads a file from /proc into a string. This is allowed on any thread as // reading from /proc does not hit the disk. Returns true if the file can be // read and is non-empty. bool ReadProcFile(const FilePath& file, std::string* buffer); +#if !defined(STARBOARD) // Take a /proc directory entry named |d_name|, and if it is the directory for // a process, convert it to a pid_t. // Returns 0 on failure. @@ -50,6 +55,7 @@ pid_t ProcDirSlotToPid(const char* d_name); // Reads /proc//stat into |buffer|. Returns true if the file can be read // and is non-empty. bool ReadProcStats(pid_t pid, std::string* buffer); +#endif // !defined(STARBOARD) // Takes |stats_data| and populates |proc_stats| with the values split by // spaces. Taking into account the 2nd field may, in itself, contain spaces. @@ -89,16 +95,21 @@ size_t GetProcStatsFieldAsSizeT(const std::vector& proc_stats, // ReadProcStats(). See GetProcStatsFieldAsInt64() for details. int64_t ReadStatsFilendGetFieldAsInt64(const FilePath& stat_file, ProcStatsFields field_num); +#if !defined(STARBOARD) int64_t ReadProcStatsAndGetFieldAsInt64(pid_t pid, ProcStatsFields field_num); +#endif // !defined(STARBOARD) int64_t ReadProcSelfStatsAndGetFieldAsInt64(ProcStatsFields field_num); +#if !defined(STARBOARD) // Same as ReadProcStatsAndGetFieldAsInt64() but for size_t values. size_t ReadProcStatsAndGetFieldAsSizeT(pid_t pid, ProcStatsFields field_num); +#endif // !defined(STARBOARD) // Returns the time that the OS started. Clock ticks are relative to this. Time GetBootTime(); +#if !defined(STARBOARD) // Returns the amount of time spent in user space since boot across all CPUs. TimeDelta GetUserCpuTimeSinceBoot(); @@ -130,6 +141,7 @@ void ForEachProcessTask(base::ProcessHandle process, Lambda&& lambda) { lambda(tid, task_path); } } +#endif // !defined(STARBOARD) } // namespace internal } // namespace base diff --git a/cobalt/base/BUILD.gn b/cobalt/base/BUILD.gn index 8395cf012d65..25f2e7acdaa1 100644 --- a/cobalt/base/BUILD.gn +++ b/cobalt/base/BUILD.gn @@ -67,6 +67,8 @@ static_library("base") { "path_provider.h", "polymorphic_downcast.h", "polymorphic_equatable.h", + "process/process_metrics_helper.cc", + "process/process_metrics_helper.h", "ref_counted_lock.h", "source_location.cc", "source_location.h", @@ -117,6 +119,7 @@ target(gtest_target_type, "base_test") { "c_val_time_interval_timer_stats_test.cc", "circular_buffer_shell_unittest.cc", "fixed_size_lru_cache_test.cc", + "process/process_metrics_helper_test.cc", "statistics_test.cc", "token_test.cc", ] diff --git a/cobalt/base/process/process_metrics_helper.cc b/cobalt/base/process/process_metrics_helper.cc new file mode 100644 index 000000000000..409e828654e7 --- /dev/null +++ b/cobalt/base/process/process_metrics_helper.cc @@ -0,0 +1,178 @@ +// Copyright 2024 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cobalt/base/process/process_metrics_helper.h" + +#include +#include +#include +#include +#include + +#include "base/files/file_enumerator.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/memory/ptr_util.h" +#include "base/process/internal_linux.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/time/time.h" + +namespace base { + +namespace { + +static std::atomic clock_ticks_per_s{0}; + +ProcessMetricsHelper::ReadCallback GetReadCallback(const FilePath& path) { + return BindOnce( + [](const FilePath& path) -> absl::optional { + std::string contents; + if (!ReadFileToString(path, &contents)) return absl::nullopt; + return std::move(contents); + }, + path); +} + +double CalculateCPUUsageSeconds(const std::string& utime_string, + const std::string& stime_string, + int ticks_per_s) { + DCHECK_NE(ticks_per_s, 0); + double utime; + if (!StringToDouble(utime_string, &utime)) return 0.0; + double stime; + if (!StringToDouble(stime_string, &stime)) return 0.0; + return (utime + stime) / static_cast(ticks_per_s); +} + +} // namespace + +// static +int ProcessMetricsHelper::GetClockTicksPerS() { + return clock_ticks_per_s.load(); +} + +// static +int ProcessMetricsHelper::GetClockTicksPerS(ReadCallback uptime_callback, + ReadCallback stat_callback) { + double uptime = 0.0; + { + auto uptime_contents = std::move(uptime_callback).Run(); + if (!uptime_contents) return 0; + auto parts = SplitString(*uptime_contents, " ", TRIM_WHITESPACE, + SPLIT_WANT_NONEMPTY); + if (parts.size() == 0 || !StringToDouble(parts[0], &uptime) || + uptime == 0.0) { + return 0; + } + } + + auto fields = GetProcStatFields(std::move(stat_callback), + {internal::ProcStatsFields::VM_STARTTIME}); + if (fields.size() != 1) return 0; + double starttime; + if (!StringToDouble(fields[0], &starttime) || starttime == 0.0) return 0; + int ticks_per_s = static_cast(starttime / uptime); + int nearest_tens = 10 * ((ticks_per_s + 5) / 10); + return nearest_tens; +} + +// static +void ProcessMetricsHelper::PopulateClockTicksPerS() { + DCHECK_EQ(clock_ticks_per_s.load(), 0); + clock_ticks_per_s.store( + GetClockTicksPerS(GetReadCallback(FilePath("/proc/uptime")), + GetReadCallback(FilePath("/proc/self/stat")))); +} + +// static +TimeDelta ProcessMetricsHelper::GetCumulativeCPUUsage() { + int ticks_per_s = clock_ticks_per_s.load(); + if (ticks_per_s == 0) return TimeDelta(); + return GetCPUUsage(FilePath("/proc/self"), ticks_per_s); +} + +// static +Value ProcessMetricsHelper::GetCumulativeCPUUsagePerThread() { + int ticks_per_s = clock_ticks_per_s.load(); + if (ticks_per_s == 0) return Value(); + Value::List cpu_per_thread; + FileEnumerator file_enum(FilePath("/proc/self/task"), /*recursive=*/false, + FileEnumerator::DIRECTORIES); + for (FilePath path = file_enum.Next(); !path.empty(); + path = file_enum.Next()) { + Fields fields = + GetProcStatFields(path, {0, internal::ProcStatsFields::VM_COMM, + internal::ProcStatsFields::VM_UTIME, + internal::ProcStatsFields::VM_STIME}); + if (fields.size() != 4) continue; + int id; + if (!StringToInt(fields[0], &id)) continue; + Value::Dict entry = + Value::Dict() + .Set("id", id) + .Set("name", fields[1]) + .Set("utime", fields[2]) + .Set("stime", fields[3]) + .Set("usage_seconds", + CalculateCPUUsageSeconds(fields[2], fields[3], ticks_per_s)); + cpu_per_thread.Append(std::move(entry)); + } + return Value(std::move(cpu_per_thread)); +} + +// static +ProcessMetricsHelper::Fields ProcessMetricsHelper::GetProcStatFields( + ReadCallback read_callback, std::initializer_list indices) { + absl::optional contents = std::move(read_callback).Run(); + if (!contents) return Fields(); + + std::vector proc_stats; + if (!internal::ParseProcStats(*contents, &proc_stats)) return Fields(); + + Fields fields; + for (int index : indices) { + if (index < 0 || index >= proc_stats.size()) return Fields(); + fields.push_back(std::move(proc_stats[index])); + } + return std::move(fields); +} + +// static +ProcessMetricsHelper::Fields ProcessMetricsHelper::GetProcStatFields( + const FilePath& path, std::initializer_list indices) { + return ProcessMetricsHelper::GetProcStatFields( + GetReadCallback(path.Append("stat")), indices); +} + +// static +TimeDelta ProcessMetricsHelper::GetCPUUsage(ReadCallback read_callback, + int ticks_per_s) { + auto fields = ProcessMetricsHelper::GetProcStatFields( + std::move(read_callback), {internal::ProcStatsFields::VM_UTIME, + internal::ProcStatsFields::VM_STIME}); + if (fields.size() != 2) return TimeDelta(); + return TimeDelta::FromSecondsD( + CalculateCPUUsageSeconds(fields[0], fields[1], ticks_per_s)); +} + +// static +TimeDelta ProcessMetricsHelper::GetCPUUsage(const FilePath& path, + int ticks_per_s) { + return ProcessMetricsHelper::GetCPUUsage(GetReadCallback(path.Append("stat")), + ticks_per_s); +} + +} // namespace base diff --git a/cobalt/base/process/process_metrics_helper.h b/cobalt/base/process/process_metrics_helper.h new file mode 100644 index 000000000000..4635506c9ea3 --- /dev/null +++ b/cobalt/base/process/process_metrics_helper.h @@ -0,0 +1,54 @@ +// Copyright 2024 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef COBALT_BASE_PROCESS_PROCESS_METRICS_HELPER_H_ +#define COBALT_BASE_PROCESS_PROCESS_METRICS_HELPER_H_ + +#include +#include +#include + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/threading/platform_thread.h" +#include "base/time/time.h" +#include "base/values.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace base { + +class ProcessMetricsHelper { + public: + using CPUUsagePerThread = std::vector>; + using ReadCallback = OnceCallback()>; + using Fields = std::vector; + + static int GetClockTicksPerS(); + static void PopulateClockTicksPerS(); + static TimeDelta GetCumulativeCPUUsage(); + static Value GetCumulativeCPUUsagePerThread(); + + private: + friend class ProcessMetricsHelperTest; + + static int GetClockTicksPerS(ReadCallback, ReadCallback); + static Fields GetProcStatFields(ReadCallback, std::initializer_list); + static Fields GetProcStatFields(const FilePath&, std::initializer_list); + static TimeDelta GetCPUUsage(ReadCallback, int); + static TimeDelta GetCPUUsage(const FilePath&, int); +}; + +} // namespace base + +#endif // COBALT_BASE_PROCESS_PROCESS_METRICS_HELPER_H_ diff --git a/cobalt/base/process/process_metrics_helper_test.cc b/cobalt/base/process/process_metrics_helper_test.cc new file mode 100644 index 000000000000..484571105662 --- /dev/null +++ b/cobalt/base/process/process_metrics_helper_test.cc @@ -0,0 +1,223 @@ +// Copyright 2024 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cobalt/base/process/process_metrics_helper.h" + +#include +#include +#include +#include +#include +#include + +#include "base/bind.h" +#include "base/containers/contains.h" +#include "base/strings/string_number_conversions.h" +#include "base/threading/thread.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { + +namespace { + +void BusyWork(std::vector* vec) { + for (int i = 0; i < 100000; ++i) { + vec->push_back(NumberToString(i)); + } +} + +bool WorkUntilUsageIsDetected(Thread* thread) { + // Attempt to do work on |thread| until CPU usage can be detected. + for (int i = 0; i < 10; i++) { + std::vector vec; + thread->task_runner()->PostTask(FROM_HERE, BindOnce(&BusyWork, &vec)); + thread->FlushForTesting(); + auto cpu_per_thread = + ProcessMetricsHelper::GetCumulativeCPUUsagePerThread(); + auto* list = cpu_per_thread.GetIfList(); + EXPECT_NE(list, nullptr); + int found_populated = 0; + for (auto& entry_value : *list) { + base::Value::Dict* entry = entry_value.GetIfDict(); + EXPECT_NE(entry, nullptr); + int id = entry->FindInt("id").value(); + if (id == thread->GetThreadId()) { + int utime; + if (!StringToInt(*(entry->FindString("utime")), &utime)) return false; + int stime; + if (!StringToInt(*(entry->FindString("stime")), &stime)) return false; + if (utime + stime > 0) { + return true; + } + } + } + } + return false; +} + +void CheckThreadUsage(const base::Value::List& list, PlatformThreadId thread_id, + const std::string& thread_name) { + for (auto& entry_value : list) { + const base::Value::Dict* entry = entry_value.GetIfDict(); + EXPECT_NE(entry, nullptr); + EXPECT_TRUE(entry->contains("id")); + EXPECT_TRUE(entry->contains("name")); + EXPECT_TRUE(entry->contains("utime")); + EXPECT_TRUE(entry->contains("stime")); + EXPECT_TRUE(entry->contains("usage_seconds")); + int id = entry->FindInt("id").value(); + if (id == thread_id) { + EXPECT_EQ(thread_name, *(entry->FindString("name"))); + EXPECT_GT(entry->FindString("utime")->size(), 0); + EXPECT_GT(entry->FindString("stime")->size(), 0); + EXPECT_GT(entry->FindDouble("usage_seconds").value_or(0.0), 0.0); + return; + } + } + EXPECT_TRUE(false) << "Thread not found in usage list."; +} + +} // namespace + +class ProcessMetricsHelperTest : public testing::Test { + public: + static void SetUpTestSuite() { + ProcessMetricsHelper::PopulateClockTicksPerS(); + } + + ProcessMetricsHelper::Fields GetProcStatFields( + ProcessMetricsHelper::ReadCallback cb, + std::initializer_list indices) { + return ProcessMetricsHelper::GetProcStatFields(std::move(cb), indices); + } + + int GetClockTicksPerS(ProcessMetricsHelper::ReadCallback uptime_callback, + ProcessMetricsHelper::ReadCallback stat_callback) { + return ProcessMetricsHelper::GetClockTicksPerS(std::move(uptime_callback), + std::move(stat_callback)); + } + + TimeDelta GetCPUUsage(ProcessMetricsHelper::ReadCallback stat_callback, + int ticks_per_s) { + return ProcessMetricsHelper::GetCPUUsage(std::move(stat_callback), + ticks_per_s); + } +}; + +ProcessMetricsHelper::ReadCallback GetNulloptCallback() { + return BindOnce( + []() -> absl::optional { return absl::nullopt; }); +} + +ProcessMetricsHelper::ReadCallback GetUptimeCallback() { + return BindOnce([]() -> absl::optional { + // Example of `cat /proc/uptime`. First number is uptime in seconds. + return "1635667.97 155819443.97"; + }); +} + +ProcessMetricsHelper::ReadCallback GetStatCallback() { + return BindOnce([]() -> absl::optional { + // Example of `cat /proc//stat`. The format is described in `man proc` + // or https://man7.org/linux/man-pages/man5/proc.5.html. `utime`, `stime`, + // and `starttime` are in clock ticks (commonly 100Hz). + return "3669677 (name with )( ) R 477564 3669677 477564 34817 3669677 " + "4194304 91 0 0 0 1 3 0 0 20 0 1 0 163348716 9076736 384 " + "18446744073709551615 94595846197248 94595846217801 140722879734192 " + "0 0 0 0 0 0 0 0 0 17 52 0 0 0 0 0 94595846237232 94595846238880 " + "94595860643840 140722879736274 140722879736294 140722879736294 " + "140722879741931 0"; + }); +} + +TEST_F(ProcessMetricsHelperTest, GetProcStatFields) { + auto fields = GetProcStatFields(GetStatCallback(), {0, 1, 2, 21, 51}); + EXPECT_EQ(5, fields.size()); + EXPECT_EQ("3669677", fields[0]); + EXPECT_EQ("name with )( ", fields[1]); + EXPECT_EQ("R", fields[2]); + EXPECT_EQ("163348716", fields[3]); + EXPECT_EQ("0", fields[4]); + + fields = GetProcStatFields(GetStatCallback(), {0}); + EXPECT_EQ(1, fields.size()); + EXPECT_EQ("3669677", fields[0]); + + fields = GetProcStatFields(GetStatCallback(), {0, 52}); + EXPECT_EQ(0, fields.size()); + + fields = GetProcStatFields(GetNulloptCallback(), {0}); + EXPECT_EQ(0, fields.size()); +} + +TEST_F(ProcessMetricsHelperTest, GetClockTicksPerSWithCallbacks) { + EXPECT_EQ(0, GetClockTicksPerS(GetNulloptCallback(), GetNulloptCallback())); + EXPECT_EQ(0, GetClockTicksPerS(GetNulloptCallback(), GetStatCallback())); + EXPECT_EQ(0, GetClockTicksPerS(GetUptimeCallback(), GetNulloptCallback())); + EXPECT_EQ(100, GetClockTicksPerS(GetUptimeCallback(), GetStatCallback())); +} + +TEST_F(ProcessMetricsHelperTest, GetClockTicksPerS) { + EXPECT_EQ(100, ProcessMetricsHelper::GetClockTicksPerS()); +} + +TEST_F(ProcessMetricsHelperTest, GetCumulativeCPUUsage) { + TimeDelta usage = ProcessMetricsHelper::GetCumulativeCPUUsage(); + EXPECT_GE(usage.InMicroseconds(), 0); +} + +TEST_F(ProcessMetricsHelperTest, GetCPUUsage) { + TimeDelta usage = GetCPUUsage(GetStatCallback(), 100); + EXPECT_EQ(40, usage.InMilliseconds()); +} + +TEST_F(ProcessMetricsHelperTest, GetCumulativeCPUUsagePerThread) { + int initial_num_threads; + { + base::Value cpu_per_thread = + ProcessMetricsHelper::GetCumulativeCPUUsagePerThread(); + base::Value::List* list = cpu_per_thread.GetIfList(); + initial_num_threads = list ? list->size() : 0; + } + + Thread thread1("thread1"); + Thread thread2("thread2"); + Thread thread3("thread3"); + + thread1.StartAndWaitForTesting(); + thread2.StartAndWaitForTesting(); + thread3.StartAndWaitForTesting(); + + ASSERT_TRUE(thread1.IsRunning()); + ASSERT_TRUE(thread2.IsRunning()); + ASSERT_TRUE(thread3.IsRunning()); + + EXPECT_TRUE(WorkUntilUsageIsDetected(&thread1)); + EXPECT_TRUE(WorkUntilUsageIsDetected(&thread2)); + EXPECT_TRUE(WorkUntilUsageIsDetected(&thread3)); + + auto cpu_per_thread = ProcessMetricsHelper::GetCumulativeCPUUsagePerThread(); + auto* list = cpu_per_thread.GetIfList(); + EXPECT_NE(list, nullptr); + EXPECT_EQ(initial_num_threads + 3, list->size()); + CheckThreadUsage(*list, thread1.GetThreadId(), thread1.thread_name()); + CheckThreadUsage(*list, thread2.GetThreadId(), thread2.thread_name()); + CheckThreadUsage(*list, thread3.GetThreadId(), thread3.thread_name()); + thread1.Stop(); + thread2.Stop(); + thread3.Stop(); +} + +} // namespace base diff --git a/starboard/shared/win32/test_filters.py b/starboard/shared/win32/test_filters.py index 352adbb1c886..8236713eac4e 100644 --- a/starboard/shared/win32/test_filters.py +++ b/starboard/shared/win32/test_filters.py @@ -23,6 +23,13 @@ # implementation. 'SbTimeZoneGetNameTest.IsIANAFormat', ], + 'base_test': [ + # The `ProcessMetricsHelper` depends on the virtual files in /proc. + # This is limited to Linux platforms. + # See https://man7.org/linux/man-pages/man5/proc.5.html. + 'ProcessMetricsHelperTest.GetClockTicksPerS', + 'ProcessMetricsHelperTest.GetCumulativeCPUUsagePerThread', + ], }