diff --git a/base/BUILD.gn b/base/BUILD.gn index 3617d5496b3c..8ca52c4d9557 100644 --- a/base/BUILD.gn +++ b/base/BUILD.gn @@ -1244,8 +1244,6 @@ component("base") { "native_library_win.cc", "os_compat_android.cc", "os_compat_android.h", - "process/internal_linux.cc", - "process/internal_linux.h", "process/kill.cc", "process/kill.h", "process/kill_mac.cc", diff --git a/base/process/internal_linux.cc b/base/process/internal_linux.cc index 49f28fcc9a2d..49a66f912a09 100644 --- a/base/process/internal_linux.cc +++ b/base/process/internal_linux.cc @@ -32,6 +32,7 @@ const char kProcDir[] = "/proc"; const char kStatFile[] = "stat"; +#if !defined(STARBOARD) FilePath GetProcPidDir(pid_t pid) { return FilePath(kProcDir).Append(IntToString(pid)); } @@ -55,6 +56,7 @@ pid_t ProcDirSlotToPid(const char* d_name) { } return pid; } +#endif // !defined(STARBOARD) bool ReadProcFile(const FilePath& file, std::string* buffer) { buffer->clear(); @@ -68,10 +70,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) { @@ -97,7 +101,11 @@ bool ParseProcStats(const std::string& stats_data, proc_stats->clear(); // PID. +#if defined(STARBOARD) + proc_stats->push_back(stats_data.substr(0, open_parens_idx - 1)); +#else proc_stats->push_back(stats_data.substr(0, open_parens_idx)); +#endif // Process name without parentheses. proc_stats->push_back( stats_data.substr(open_parens_idx + 1, @@ -150,16 +158,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; @@ -170,6 +181,7 @@ size_t ReadProcStatsAndGetFieldAsSizeT(pid_t pid, return 0; return GetProcStatsFieldAsSizeT(proc_stats, field_num); } +#endif // !defined(STARBOARD) Time GetBootTime() { FilePath path("/proc/stat"); @@ -187,6 +199,7 @@ Time GetBootTime() { return Time::FromTimeT(btime); } +#if !defined(STARBOARD) TimeDelta GetUserCpuTimeSinceBoot() { FilePath path("/proc/stat"); std::string contents; @@ -227,6 +240,7 @@ TimeDelta ClockTicksToTimeDelta(int clock_ticks) { return TimeDelta::FromMicroseconds( 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 8139c18bfa62..91c445345f32 100644 --- a/base/process/internal_linux.h +++ b/base/process/internal_linux.h @@ -8,7 +8,9 @@ #ifndef BASE_PROCESS_INTERNAL_LINUX_H_ #define BASE_PROCESS_INTERNAL_LINUX_H_ +#if !defined(STARBOARD) #include +#endif // !defined(STARBOARD) #include "base/files/file_path.h" #include "starboard/types.h" @@ -26,6 +28,7 @@ extern const char kProcDir[]; // "stat" extern const char kStatFile[]; +#if !defined(STARBOARD) // Returns a FilePath to "/proc/pid". base::FilePath GetProcPidDir(pid_t pid); @@ -38,6 +41,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. @@ -77,21 +81,27 @@ 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(); // Converts Linux clock ticks to a wall time delta. TimeDelta ClockTicksToTimeDelta(int clock_ticks); +#endif // !defined(STARBOARD) } // namespace internal } // namespace base diff --git a/cobalt/base/BUILD.gn b/cobalt/base/BUILD.gn index 1f78912fe63b..d4cc9400f8f7 100644 --- a/cobalt/base/BUILD.gn +++ b/cobalt/base/BUILD.gn @@ -68,6 +68,8 @@ static_library("base") { "poller.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", @@ -116,6 +118,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..9300ab810362 --- /dev/null +++ b/cobalt/base/process/process_metrics_helper.cc @@ -0,0 +1,182 @@ +// 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/files/file_enumerator.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/memory/ptr_util.h" +#include "base/optional.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) -> Optional { + std::string contents; + if (!ReadFileToString(path, &contents)) return nullopt; + return 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 current_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], ¤t_uptime) || + current_uptime == 0.0) { + return 0; + } + } + + auto fields = GetProcStatFields(std::move(stat_callback), + {internal::ProcStatsFields::VM_STARTTIME}); + if (fields.size() != 1) return 0; + double process_starttime; + if (!StringToDouble(fields[0], &process_starttime) || + process_starttime == 0.0) + return 0; + double ticks_per_s = process_starttime / current_uptime; + int rounded_up = 10 * static_cast(std::ceil(ticks_per_s / 10.0)); + return rounded_up; +} + +// 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(); + ListValue 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; + DictionaryValue entry; + entry.SetKey("id", Value(id)); + entry.SetKey("name", Value(fields[1])); + entry.SetKey("utime", Value(fields[2])); + entry.SetKey("stime", Value(fields[3])); + entry.SetKey("usage_seconds", Value(CalculateCPUUsageSeconds( + fields[2], fields[3], ticks_per_s))); + cpu_per_thread.GetList().push_back(std::move(entry)); + } + return std::move(cpu_per_thread); +} + +// static +ProcessMetricsHelper::Fields ProcessMetricsHelper::GetProcStatFields( + ReadCallback read_callback, std::initializer_list indices) { + auto 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..b258c992daa2 --- /dev/null +++ b/cobalt/base/process/process_metrics_helper.h @@ -0,0 +1,56 @@ +// 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 + +#include "base/bind.h" +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/optional.h" +#include "base/threading/platform_thread.h" +#include "base/time/time.h" +#include "base/values.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..27b229e12d03 --- /dev/null +++ b/cobalt/base/process/process_metrics_helper_test.cc @@ -0,0 +1,226 @@ +// 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/optional.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(); + EXPECT_TRUE(cpu_per_thread.is_list()); + auto& list = cpu_per_thread.GetList(); + int found_populated = 0; + for (auto& entry_value : list) { + EXPECT_TRUE(entry_value.is_dict()); + base::Value* id_value = entry_value.FindKey("id"); + EXPECT_NE(nullptr, id_value); + if (id_value->GetInt() == thread->GetThreadId()) { + int utime; + if (!StringToInt(entry_value.FindKey("utime")->GetString(), &utime)) + return false; + int stime; + if (!StringToInt(entry_value.FindKey("stime")->GetString(), &stime)) + return false; + if (utime + stime > 0) { + return true; + } + } + } + } + return false; +} + +void CheckThreadUsage(const base::Value::ListStorage& list, + PlatformThreadId thread_id, + const std::string& thread_name) { + for (auto& entry_value : list) { + EXPECT_TRUE(entry_value.is_dict()); + auto* id_value = entry_value.FindKey("id"); + EXPECT_NE(nullptr, id_value); + EXPECT_NE(nullptr, entry_value.FindKey("name")); + EXPECT_NE(nullptr, entry_value.FindKey("utime")); + EXPECT_NE(nullptr, entry_value.FindKey("stime")); + EXPECT_NE(nullptr, entry_value.FindKey("usage_seconds")); + EXPECT_TRUE(id_value->is_int()); + if (id_value->is_int() && id_value->GetInt() == thread_id) { + EXPECT_EQ(thread_name, entry_value.FindKey("name")->GetString()); + EXPECT_GT(entry_value.FindKey("utime")->GetString().size(), 0); + EXPECT_GT(entry_value.FindKey("stime")->GetString().size(), 0); + EXPECT_GT(entry_value.FindKey("usage_seconds")->GetDouble(), 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( + []() -> base::Optional { return base::nullopt; }); +} + +ProcessMetricsHelper::ReadCallback GetUptimeCallback() { + return BindOnce([]() -> base::Optional { + // Example of `cat /proc/uptime`. First number is uptime in seconds. + return "1635667.97 155819443.97"; + }); +} + +ProcessMetricsHelper::ReadCallback GetStatCallback() { + return BindOnce([]() -> base::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(); + initial_num_threads = + cpu_per_thread.is_list() ? cpu_per_thread.GetList().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(); + EXPECT_TRUE(cpu_per_thread.is_list()); + auto& list = cpu_per_thread.GetList(); + 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/cobalt/black_box_tests/black_box_tests.py b/cobalt/black_box_tests/black_box_tests.py index 58e042f601b3..79680844cd87 100755 --- a/cobalt/black_box_tests/black_box_tests.py +++ b/cobalt/black_box_tests/black_box_tests.py @@ -74,6 +74,7 @@ _TESTS_NO_SIGNAL = [ 'allow_eval', 'compression_test', + 'cpu_usage_tracker_test', 'default_site_can_load', 'disable_eval_with_csp', 'h5vcc_storage_write_verify_test', diff --git a/cobalt/black_box_tests/testdata/cpu_usage_tracker_test.html b/cobalt/black_box_tests/testdata/cpu_usage_tracker_test.html new file mode 100644 index 000000000000..7053f0bbaf3a --- /dev/null +++ b/cobalt/black_box_tests/testdata/cpu_usage_tracker_test.html @@ -0,0 +1,147 @@ + + + + + + + + + + diff --git a/cobalt/black_box_tests/tests/cpu_usage_tracker_test.py b/cobalt/black_box_tests/tests/cpu_usage_tracker_test.py new file mode 100644 index 000000000000..020ebe0059be --- /dev/null +++ b/cobalt/black_box_tests/tests/cpu_usage_tracker_test.py @@ -0,0 +1,47 @@ +# 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. +"""Test CpuUsageTracker config and CVals.""" + +from cobalt.black_box_tests import black_box_tests +from cobalt.black_box_tests.threaded_web_server import ThreadedWebServer +import logging + +PLATFORMS_SUPPORTED = [ + 'linux-x64x11', + 'linux-x64x11-egl', + 'linux-x64x11-gcc-6-3', + 'linux-x64x11-skia', + 'android-arm', + 'android-arm64', + 'android-arm64-vulkan', + 'android-x86', + 'raspi-2', + 'raspi-2-skia', + 'linux-x64x11-clang-crosstool', +] + + +class CpuUsageTrackerTest(black_box_tests.BlackBoxTestCase): + + def test_cpu_usage_tracker(self): + if self.launcher_params.platform not in PLATFORMS_SUPPORTED: + logging.warning('Blackbox tests disabled for platform:%s', + self.launcher_params.platform) + return + + with ThreadedWebServer(binding_address=self.GetBindingAddress()) as server: + url = server.GetURL(file_name='testdata/cpu_usage_tracker_test.html') + with self.CreateCobaltRunner(url=url) as runner: + runner.WaitForJSTestsSetup() + self.assertTrue(runner.JSTestsSucceeded()) diff --git a/cobalt/browser/BUILD.gn b/cobalt/browser/BUILD.gn index 412247af8f1d..cf6148ae3e69 100644 --- a/cobalt/browser/BUILD.gn +++ b/cobalt/browser/BUILD.gn @@ -151,6 +151,7 @@ static_library("browser") { deps = [ ":bindings", ":browser_switches", + ":cpu_usage_tracker", ":generated_bindings", ":generated_types", "//cobalt/audio", @@ -231,6 +232,19 @@ static_library("browser") { } } +static_library("cpu_usage_tracker") { + sources = [ + "cpu_usage_tracker.cc", + "cpu_usage_tracker.h", + ] + + deps = [ + "//base", + "//cobalt/base", + "//cobalt/persistent_storage:persistent_settings", + ] +} + target(gtest_target_type, "browser_test") { testonly = true has_pedantic_warnings = true diff --git a/cobalt/browser/browser_module.cc b/cobalt/browser/browser_module.cc index f168f1b3bf45..4eb7a96eac90 100644 --- a/cobalt/browser/browser_module.cc +++ b/cobalt/browser/browser_module.cc @@ -35,6 +35,7 @@ #include "cobalt/base/init_cobalt.h" #include "cobalt/base/source_location.h" #include "cobalt/base/tokens.h" +#include "cobalt/browser/cpu_usage_tracker.h" #include "cobalt/browser/on_screen_keyboard_starboard_bridge.h" #include "cobalt/browser/screen_shot_writer.h" #include "cobalt/browser/switches.h" @@ -360,6 +361,8 @@ BrowserModule::BrowserModule(const GURL& url, options_.web_module_options.injected_global_object_attributes["h5vcc"] = base::Bind(&BrowserModule::CreateH5vccCallback, base::Unretained(this)); + CpuUsageTracker::GetInstance()->Initialize(options_.persistent_settings); + if (command_line->HasSwitch(switches::kDisableTimerResolutionLimit)) { options_.web_module_options.limit_performance_timer_resolution = false; } diff --git a/cobalt/browser/cpu_usage_tracker.cc b/cobalt/browser/cpu_usage_tracker.cc new file mode 100644 index 000000000000..2f5475fccc93 --- /dev/null +++ b/cobalt/browser/cpu_usage_tracker.cc @@ -0,0 +1,412 @@ +// 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/browser/cpu_usage_tracker.h" + +#include +#include +#include +#include +#include +#include + +#include "base/bind.h" +#include "base/json/json_writer.h" +#include "base/memory/singleton.h" +#include "base/optional.h" +#include "base/strings/stringprintf.h" +#include "base/threading/thread_task_runner_handle.h" +#include "cobalt/base/polymorphic_downcast.h" +#include "cobalt/base/process/process_metrics_helper.h" + +namespace cobalt { +namespace browser { + +namespace { + +const char kIntervals[] = "cpu_usage_tracker_intervals"; +const char kOneTimeTracking[] = "cpu_usage_tracker_one_time_tracking"; +const char kIntervalsEnabled[] = "cpu_usage_tracker_intervals_enabled"; + +enum IntervalType { + PER_THREAD, + TOTAL, +}; + +struct Interval { + Interval(IntervalType type, int seconds) : type(type), seconds(seconds) {} + + IntervalType type; + int seconds; +}; + +struct Config { + Config() {} + Config(std::initializer_list intervals) : intervals(intervals) {} + + std::vector intervals; +}; + +static std::atomic next_id{0}; + +base::Value GetDefaultIntervalsValue() { + base::DictionaryValue interval_per_thread_2; + interval_per_thread_2.SetKey("type", base::Value("per_thread")); + interval_per_thread_2.SetKey("seconds", base::Value(2)); + base::DictionaryValue interval_per_thread_30; + interval_per_thread_30.SetKey("type", base::Value("per_thread")); + interval_per_thread_30.SetKey("seconds", base::Value(30)); + base::DictionaryValue interval_total_2; + interval_total_2.SetKey("type", base::Value("total")); + interval_total_2.SetKey("seconds", base::Value(2)); + base::DictionaryValue interval_total_30; + interval_total_30.SetKey("type", base::Value("total")); + interval_total_30.SetKey("seconds", base::Value(30)); + base::ListValue list_value; + base::Value::ListStorage& list_storage = list_value.GetList(); + list_storage.push_back(std::move(interval_per_thread_2)); + list_storage.push_back(std::move(interval_per_thread_30)); + list_storage.push_back(std::move(interval_total_2)); + list_storage.push_back(std::move(interval_total_30)); + return std::move(list_value); +} + +std::vector GetDefaultIntervals() { + return std::vector({ + Interval(IntervalType::PER_THREAD, /*seconds=*/2), + Interval(IntervalType::PER_THREAD, /*seconds=*/30), + Interval(IntervalType::TOTAL, /*seconds=*/2), + Interval(IntervalType::TOTAL, /*seconds=*/30), + }); +} + +base::Optional> ParseIntervals( + const base::Value& interval_values) { + std::vector intervals; + for (auto& item_value : interval_values.GetList()) { + if (!item_value.is_dict()) return base::nullopt; + const base::Value* raw_type = item_value.FindKey("type"); + if (!raw_type || !raw_type->is_string()) return base::nullopt; + IntervalType type; + if (raw_type->GetString().compare("total") == 0) { + type = IntervalType::TOTAL; + } else if (raw_type->GetString().compare("per_thread") == 0) { + type = IntervalType::PER_THREAD; + } else { + return base::nullopt; + } + const base::Value* seconds = item_value.FindKey("seconds"); + if (!seconds || seconds->GetInt() == 0) return base::nullopt; + intervals.emplace_back(type, seconds->GetInt()); + } + return std::move(intervals); +} + +} // namespace + +// static +CpuUsageTracker* CpuUsageTracker::GetInstance() { + return base::Singleton>::get(); +} + +CpuUsageTracker::CpuUsageTracker() + : storage_(nullptr), + task_runner_(base::ThreadTaskRunnerHandle::Get()), + intervals_enabled_(false), + one_time_tracking_started_(false), + cval_one_time_tracking_per_thread_( + std::make_unique>( + "CPU.PerThread.Usage.OneTime", + /*initial_value=*/"", /*description=*/"")), + cval_one_time_tracking_total_( + std::make_unique>( + "CPU.Total.Usage.OneTime", + /*initial_value=*/0.0, /*description=*/"")) { + base::MessageLoopCurrent::Get()->AddDestructionObserver(this); +} + +CpuUsageTracker::~CpuUsageTracker() { ClearIntervalContexts(); } + +void CpuUsageTracker::Initialize( + persistent_storage::PersistentSettings* storage) { + DCHECK_EQ(storage_, nullptr); + storage_ = storage; + task_runner_->PostTask(FROM_HERE, + base::BindOnce(&CpuUsageTracker::InitializeAsync, + base::Unretained(this))); +} + +void CpuUsageTracker::InitializeAsync() { + base::ProcessMetricsHelper::PopulateClockTicksPerS(); + UpdateIntervalsDefinition(std::make_unique( + storage_->GetPersistentSettingAsList(kIntervals))); + UpdateIntervalsEnabled( + storage_->GetPersistentSettingAsBool(kIntervalsEnabled, false)); + if (storage_->GetPersistentSettingAsBool(kOneTimeTracking, false)) { + StartOneTimeTracking(); + } +} + +void CpuUsageTracker::ClearIntervalContexts() { + DCHECK(task_runner_->RunsTasksInCurrentSequence()); + lock_.AssertAcquired(); + for (IdIntervalContextPair& pair : id_to_interval_context_) { + std::unique_ptr interval_context = std::move(pair.second); + if (interval_context->timer) interval_context->timer->Stop(); + interval_context->timer.reset(); + if (interval_context->type == IntervalContextType::PER_THREAD) { + auto* per_thread_context = + static_cast(interval_context.get()); + per_thread_context->cval.reset(); + per_thread_context->previous.reset(); + } else { + auto* total_context = + static_cast(interval_context.get()); + total_context->cval.reset(); + } + } + id_to_interval_context_.clear(); +} + +void CpuUsageTracker::CreatePerThreadIntervalContext(int interval_seconds) { + DCHECK(task_runner_->RunsTasksInCurrentSequence()); + lock_.AssertAcquired(); + + uint64_t id = next_id.fetch_add(1); + auto context = std::make_unique(); + auto name = base::StringPrintf("CPU.PerThread.Usage.IntervalSeconds.%d", + interval_seconds); + context->cval = std::make_unique>( + name, + /*initial_value=*/"", /*description=*/""); + context->timer = std::make_unique( + FROM_HERE, base::TimeDelta::FromSecondsD(interval_seconds), + base::BindRepeating(&CpuUsageTracker::PerThreadIntervalTask, + base::Unretained(this), id)); + context->previous = base::Value::ToUniquePtrValue( + base::ProcessMetricsHelper::GetCumulativeCPUUsagePerThread()); + id_to_interval_context_.emplace(id, std::move(context)); + if (intervals_enabled_) { + // Start repeating timer with existing interval duration and task. + id_to_interval_context_[id]->timer->Reset(); + } +} + +void CpuUsageTracker::CreateTotalIntervalContext(int interval_seconds) { + DCHECK(task_runner_->RunsTasksInCurrentSequence()); + lock_.AssertAcquired(); + uint64_t id = next_id.fetch_add(1); + auto context = std::make_unique(); + auto name = base::StringPrintf("CPU.Total.Usage.IntervalSeconds.%d", + interval_seconds); + context->cval = std::make_unique>( + name, + /*initial_value=*/0.0, /*description=*/""); + context->timer = std::make_unique( + FROM_HERE, base::TimeDelta::FromSecondsD(interval_seconds), + base::BindRepeating(&CpuUsageTracker::TotalIntervalTask, + base::Unretained(this), id)); + context->previous = base::ProcessMetricsHelper::GetCumulativeCPUUsage(); + id_to_interval_context_.emplace(id, std::move(context)); + if (intervals_enabled_) { + // Start repeating timer with existing interval duration and task. + id_to_interval_context_[id]->timer->Reset(); + } +} + +void CpuUsageTracker::StartOneTimeTracking() { + if (!task_runner_->RunsTasksInCurrentSequence()) { + task_runner_->PostTask( + FROM_HERE, base::BindOnce(&CpuUsageTracker::StartOneTimeTracking, + base::Unretained(this))); + return; + } + base::AutoLock auto_lock(lock_); + one_time_tracking_started_ = true; + storage_->SetPersistentSetting(kOneTimeTracking, + std::make_unique(true)); + one_time_tracking_per_thread_at_start_ = base::Value::ToUniquePtrValue( + base::ProcessMetricsHelper::GetCumulativeCPUUsagePerThread()); + one_time_tracking_total_at_start_ = + base::ProcessMetricsHelper::GetCumulativeCPUUsage(); +} + +void CpuUsageTracker::StopAndCaptureOneTimeTracking() { + if (!one_time_tracking_started_) return; + if (!task_runner_->RunsTasksInCurrentSequence()) { + task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&CpuUsageTracker::StopAndCaptureOneTimeTracking, + base::Unretained(this))); + return; + } + base::AutoLock auto_lock(lock_); + one_time_tracking_started_ = false; + storage_->SetPersistentSetting(kOneTimeTracking, + std::make_unique(false)); + { + base::Value current = + std::move(base::ProcessMetricsHelper::GetCumulativeCPUUsagePerThread()); + std::string serialized; + base::DictionaryValue value; + value.SetKey("previous", one_time_tracking_per_thread_at_start_->Clone()); + value.SetKey("current", current.Clone()); + bool serialized_success = base::JSONWriter::Write(value, &serialized); + DCHECK(serialized_success); + *cval_one_time_tracking_per_thread_ = serialized; + } + { + base::TimeDelta current = + base::ProcessMetricsHelper::GetCumulativeCPUUsage(); + base::TimeDelta usage_during_interval = + current - one_time_tracking_total_at_start_; + *cval_one_time_tracking_total_ = usage_during_interval.InSecondsF(); + } +} + +void CpuUsageTracker::UpdateIntervalsEnabled(bool enabled) { + if (intervals_enabled_ == enabled) return; + if (!task_runner_->RunsTasksInCurrentSequence()) { + task_runner_->PostTask( + FROM_HERE, base::BindOnce(&CpuUsageTracker::UpdateIntervalsEnabled, + base::Unretained(this), enabled)); + return; + } + base::AutoLock auto_lock(lock_); + intervals_enabled_ = enabled; + storage_->SetPersistentSetting( + kIntervalsEnabled, std::make_unique(intervals_enabled_)); + for (IdIntervalContextPair& pair : id_to_interval_context_) { + IntervalContext* interval_context = pair.second.get(); + if (!interval_context->timer) continue; + if (intervals_enabled_) { + if (interval_context->type == IntervalContextType::PER_THREAD) { + auto* per_thread_context = + static_cast(interval_context); + per_thread_context->previous = base::Value::ToUniquePtrValue( + base::ProcessMetricsHelper::GetCumulativeCPUUsagePerThread()); + } else { + auto* total_context = + static_cast(interval_context); + total_context->previous = + base::ProcessMetricsHelper::GetCumulativeCPUUsage(); + } + // Start repeating timer with existing interval duration and task. + interval_context->timer->Reset(); + } else { + interval_context->timer->Stop(); + } + } +} + +void CpuUsageTracker::UpdateIntervalsDefinition( + std::unique_ptr interval_values) { + if (!task_runner_->RunsTasksInCurrentSequence()) { + task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&CpuUsageTracker::UpdateIntervalsDefinition, + base::Unretained(this), std::move(interval_values))); + return; + } + base::AutoLock auto_lock(lock_); + base::Optional> intervals; + if (interval_values) { + intervals = ParseIntervals(*interval_values); + } + if (!intervals) { + storage_->SetPersistentSetting( + kIntervals, base::Value::ToUniquePtrValue(GetDefaultIntervalsValue())); + intervals = GetDefaultIntervals(); + } else { + storage_->SetPersistentSetting(kIntervals, std::move(interval_values)); + } + ClearIntervalContexts(); + std::set total_intervals_processed; + std::set per_thread_intervals_processed; + for (const Interval& interval : intervals.value()) { + if (interval.type == IntervalType::PER_THREAD) { + if (per_thread_intervals_processed.count(interval.seconds) == 1) { + DLOG(WARNING) << "Duplicate CPU per-thread usage interval " + << interval.seconds << "."; + continue; + } + CreatePerThreadIntervalContext(interval.seconds); + } else if (interval.type == IntervalType::TOTAL) { + if (total_intervals_processed.count(interval.seconds) == 1) { + DLOG(WARNING) << "Duplicate CPU total usage interval " + << interval.seconds << "."; + continue; + } + CreateTotalIntervalContext(interval.seconds); + total_intervals_processed.insert(interval.seconds); + } else { + NOTREACHED(); + } + } +} + +void CpuUsageTracker::PerThreadIntervalTask(uint64_t id) { + base::AutoLock auto_lock(lock_); + if (id_to_interval_context_.count(id) == 0) { + NOTREACHED(); + return; + } + IntervalContext* interval_context = id_to_interval_context_[id].get(); + DCHECK_EQ(interval_context->type, IntervalContextType::PER_THREAD); + auto* per_thread_context = + static_cast(interval_context); + if (!intervals_enabled_) { + per_thread_context->timer->Stop(); + return; + } + base::Value current = + std::move(base::ProcessMetricsHelper::GetCumulativeCPUUsagePerThread()); + base::DictionaryValue value; + value.SetKey("previous", per_thread_context->previous->Clone()); + value.SetKey("current", current.Clone()); + std::string serialized; + bool success = base::JSONWriter::Write(value, &serialized); + DCHECK(success); + *(per_thread_context->cval) = serialized; + per_thread_context->previous = + base::Value::ToUniquePtrValue(std::move(current)); +} + +void CpuUsageTracker::TotalIntervalTask(uint64_t id) { + base::AutoLock auto_lock(lock_); + if (id_to_interval_context_.count(id) == 0) { + NOTREACHED(); + return; + } + IntervalContext* interval_context = id_to_interval_context_[id].get(); + DCHECK_EQ(interval_context->type, IntervalContextType::TOTAL); + auto* total_context = static_cast(interval_context); + if (!intervals_enabled_) { + total_context->timer->Stop(); + return; + } + base::TimeDelta current = base::ProcessMetricsHelper::GetCumulativeCPUUsage(); + base::TimeDelta usage_during_interval = current - total_context->previous; + *(total_context->cval) = usage_during_interval.InSecondsF(); + total_context->previous = current; +} + +void CpuUsageTracker::WillDestroyCurrentMessageLoop() { + base::AutoLock auto_lock(lock_); + ClearIntervalContexts(); +} + +} // namespace browser +} // namespace cobalt diff --git a/cobalt/browser/cpu_usage_tracker.h b/cobalt/browser/cpu_usage_tracker.h new file mode 100644 index 000000000000..10b7d394d45b --- /dev/null +++ b/cobalt/browser/cpu_usage_tracker.h @@ -0,0 +1,108 @@ +// 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_BROWSER_CPU_USAGE_TRACKER_H_ +#define COBALT_BROWSER_CPU_USAGE_TRACKER_H_ + +#include +#include +#include +#include + +#include "base/message_loop/message_loop_current.h" +#include "base/timer/timer.h" +#include "base/values.h" +#include "cobalt/base/c_val.h" +#include "cobalt/persistent_storage/persistent_settings.h" + +namespace base { +template +struct DefaultSingletonTraits; +} + +namespace cobalt { +namespace browser { + +class CpuUsageTracker : base::MessageLoopCurrent::DestructionObserver { + public: + static CpuUsageTracker* GetInstance(); + + void Initialize(persistent_storage::PersistentSettings*); + void UpdateIntervalsDefinition(std::unique_ptr); + void UpdateIntervalsEnabled(bool); + void StartOneTimeTracking(); + void StopAndCaptureOneTimeTracking(); + + private: + friend struct base::DefaultSingletonTraits; + CpuUsageTracker(); + ~CpuUsageTracker(); + + void InitializeAsync(); + void TotalIntervalTask(uint64_t id); + void PerThreadIntervalTask(uint64_t id); + void ClearIntervalContexts(); + void CreateTotalIntervalContext(int); + void CreatePerThreadIntervalContext(int); + + // CurrentThread::DestructionObserver: + void WillDestroyCurrentMessageLoop() override; + + enum IntervalContextType { + PER_THREAD, + TOTAL, + }; + + struct IntervalContext { + IntervalContextType type; + std::unique_ptr timer; + }; + + using IdIntervalContextPair = + std::pair>; + + struct PerThreadIntervalContext : IntervalContext { + PerThreadIntervalContext() { type = IntervalContextType::PER_THREAD; } + + std::unique_ptr> cval; + std::unique_ptr previous; + }; + + struct TotalIntervalContext : IntervalContext { + TotalIntervalContext() { type = IntervalContextType::TOTAL; } + + std::unique_ptr> cval; + base::TimeDelta previous; + }; + + bool intervals_enabled_; + std::map> id_to_interval_context_; + base::Lock lock_; + + persistent_storage::PersistentSettings* storage_; + scoped_refptr task_runner_; + + bool one_time_tracking_started_; + std::unique_ptr> + cval_one_time_tracking_total_; + base::TimeDelta one_time_tracking_total_at_start_; + std::unique_ptr> + cval_one_time_tracking_per_thread_; + std::unique_ptr one_time_tracking_per_thread_at_start_; +}; + +} // namespace browser +} // namespace cobalt + +#endif // COBALT_BROWSER_CPU_USAGE_TRACKER_H_ diff --git a/cobalt/h5vcc/BUILD.gn b/cobalt/h5vcc/BUILD.gn index cd17423dd93b..f226ac782b50 100644 --- a/cobalt/h5vcc/BUILD.gn +++ b/cobalt/h5vcc/BUILD.gn @@ -71,6 +71,7 @@ static_library("h5vcc") { ":script_callback_wrapper", "//cobalt/base", "//cobalt/browser:browser_switches", + "//cobalt/browser:cpu_usage_tracker", "//cobalt/browser/metrics", "//cobalt/build:cobalt_build_id", "//cobalt/cache", diff --git a/cobalt/h5vcc/h5vcc_settings.cc b/cobalt/h5vcc/h5vcc_settings.cc index fa446af4a4a2..53d76923e556 100644 --- a/cobalt/h5vcc/h5vcc_settings.cc +++ b/cobalt/h5vcc/h5vcc_settings.cc @@ -17,7 +17,10 @@ #include #include +#include +#include "base/json/json_reader.h" +#include "cobalt/browser/cpu_usage_tracker.h" #include "cobalt/network/network_module.h" namespace cobalt { @@ -106,6 +109,31 @@ bool H5vccSettings::Set(const std::string& name, SetValueType value) const { } } + if (name.compare("cpu_usage_tracker_intervals") == 0 && + value.IsType() && value.AsType().size() < 512) { + std::unique_ptr config = + base::JSONReader::Read(value.AsType()); + browser::CpuUsageTracker::GetInstance()->UpdateIntervalsDefinition( + std::move(config)); + return true; + } + if (name.compare("cpu_usage_tracker_intervals_enabled") == 0 && + value.IsType()) { + browser::CpuUsageTracker::GetInstance()->UpdateIntervalsEnabled( + value.AsType() != 0); + return true; + } + if (name.compare("cpu_usage_tracker_one_time_tracking") == 0 && + value.IsType()) { + bool started = value.AsType() != 0; + if (started) { + browser::CpuUsageTracker::GetInstance()->StartOneTimeTracking(); + } else { + browser::CpuUsageTracker::GetInstance()->StopAndCaptureOneTimeTracking(); + } + return true; + } + #if SB_IS(EVERGREEN) if (name.compare(kUpdaterMinFreeSpaceBytes) == 0 && value.IsType()) { updater_module_->SetMinFreeSpaceBytes(value.AsType());