| // Copyright 2018 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/trace_event/cpufreq_monitor_android.h" |
| |
| #include <list> |
| |
| #include <fcntl.h> |
| |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_file.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/strings/stringprintf.h" |
| #include "starboard/types.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace base { |
| namespace trace_event { |
| |
| class TestTaskRunner : public SingleThreadTaskRunner { |
| public: |
| bool PostDelayedTask(const Location& from_here, |
| OnceClosure task, |
| base::TimeDelta delay) override { |
| delayed_tasks_.push_back(std::make_pair(std::move(delay), std::move(task))); |
| return true; |
| } |
| |
| bool PostNonNestableDelayedTask(const Location& from_here, |
| OnceClosure task, |
| base::TimeDelta delay) override { |
| NOTREACHED(); |
| return false; |
| } |
| |
| bool RunsTasksInCurrentSequence() const override { return true; } |
| |
| // Returns the delay in ms for this task if there was a task to be run, |
| // and -1 if there are no tasks in the queue. |
| int64_t RunNextTask() { |
| if (delayed_tasks_.size() == 0) |
| return -1; |
| auto time_delta = delayed_tasks_.front().first; |
| std::move(delayed_tasks_.front().second).Run(); |
| delayed_tasks_.pop_front(); |
| return time_delta.InMilliseconds(); |
| } |
| |
| private: |
| ~TestTaskRunner() final {} |
| |
| std::list<std::pair<base::TimeDelta, OnceClosure>> delayed_tasks_; |
| }; |
| |
| class TestDelegate : public CPUFreqMonitorDelegate { |
| public: |
| TestDelegate(const std::string& temp_dir_path) |
| : temp_dir_path_(temp_dir_path) {} |
| |
| void set_trace_category_enabled(bool enabled) { |
| trace_category_enabled_ = enabled; |
| } |
| |
| void set_cpu_ids(const std::vector<unsigned int>& cpu_ids) { |
| cpu_ids_ = cpu_ids; |
| } |
| |
| void set_kernel_max_cpu(unsigned int kernel_max_cpu) { |
| kernel_max_cpu_ = kernel_max_cpu; |
| } |
| |
| const std::vector<std::pair<unsigned int, unsigned int>>& recorded_freqs() { |
| return recorded_freqs_; |
| } |
| |
| // CPUFreqMonitorDelegate implementation: |
| void GetCPUIds(std::vector<unsigned int>* ids) const override { |
| // Use the test values if available. |
| if (cpu_ids_.size() > 0) { |
| *ids = cpu_ids_; |
| return; |
| } |
| // Otherwise fall back to the original function. |
| CPUFreqMonitorDelegate::GetCPUIds(ids); |
| } |
| |
| void RecordFrequency(unsigned int cpu_id, unsigned int freq) override { |
| recorded_freqs_.emplace_back( |
| std::pair<unsigned int, unsigned int>(cpu_id, freq)); |
| } |
| |
| bool IsTraceCategoryEnabled() const override { |
| return trace_category_enabled_; |
| } |
| |
| std::string GetScalingCurFreqPathString(unsigned int cpu_id) const override { |
| return base::StringPrintf("%s/scaling_cur_freq%d", temp_dir_path_.c_str(), |
| cpu_id); |
| } |
| |
| std::string GetRelatedCPUsPathString(unsigned int cpu_id) const override { |
| return base::StringPrintf("%s/related_cpus%d", temp_dir_path_.c_str(), |
| cpu_id); |
| } |
| |
| unsigned int GetKernelMaxCPUs() const override { return kernel_max_cpu_; } |
| |
| protected: |
| scoped_refptr<SingleThreadTaskRunner> CreateTaskRunner() override { |
| return base::WrapRefCounted(new TestTaskRunner()); |
| } |
| |
| private: |
| // Maps CPU ID to frequency. |
| std::vector<std::pair<unsigned int, unsigned int>> recorded_freqs_; |
| |
| std::vector<unsigned int> cpu_ids_; |
| |
| bool trace_category_enabled_ = true; |
| std::string temp_dir_path_; |
| unsigned int kernel_max_cpu_ = 0; |
| }; |
| |
| class CPUFreqMonitorTest : public testing::Test { |
| public: |
| CPUFreqMonitorTest() : testing::Test() {} |
| |
| void SetUp() override { |
| temp_dir_ = std::make_unique<ScopedTempDir>(); |
| ASSERT_TRUE(temp_dir_->CreateUniqueTempDir()); |
| |
| std::string base_path = temp_dir_->GetPath().value(); |
| auto delegate = std::make_unique<TestDelegate>(base_path); |
| // Retain a pointer to the delegate since we're passing ownership to the |
| // monitor but we need to be able to modify it. |
| delegate_ = delegate.get(); |
| |
| // Can't use make_unique because it's a private constructor. |
| CPUFreqMonitor* monitor = new CPUFreqMonitor(std::move(delegate)); |
| monitor_.reset(monitor); |
| } |
| |
| void TearDown() override { |
| monitor_.reset(); |
| temp_dir_.reset(); |
| } |
| |
| void CreateDefaultScalingCurFreqFiles( |
| const std::vector<std::pair<unsigned int, unsigned int>>& frequencies) { |
| for (auto& pair : frequencies) { |
| std::string file_path = |
| delegate_->GetScalingCurFreqPathString(pair.first); |
| std::string str_freq = base::StringPrintf("%d\n", pair.second); |
| base::WriteFile(base::FilePath(file_path), str_freq.c_str(), |
| str_freq.length()); |
| } |
| } |
| |
| void CreateRelatedCPUFiles(const std::vector<unsigned int>& clusters, |
| const std::vector<std::string>& related_cpus) { |
| for (unsigned int i = 0; i < clusters.size(); i++) { |
| base::WriteFile(base::FilePath(delegate_->GetRelatedCPUsPathString(i)), |
| related_cpus[clusters[i]].c_str(), |
| related_cpus[clusters[i]].length()); |
| } |
| } |
| |
| void InitBasicCPUInfo() { |
| std::vector<std::pair<unsigned int, unsigned int>> frequencies = { |
| {0, 500}, {2, 1000}, {4, 800}, {6, 750}, |
| }; |
| std::vector<unsigned int> cpu_ids; |
| for (auto& pair : frequencies) { |
| cpu_ids.push_back(pair.first); |
| } |
| delegate()->set_cpu_ids(cpu_ids); |
| |
| CreateDefaultScalingCurFreqFiles(frequencies); |
| } |
| |
| TestTaskRunner* GetOrCreateTaskRunner() { |
| return static_cast<TestTaskRunner*>( |
| monitor_->GetOrCreateTaskRunner().get()); |
| } |
| |
| CPUFreqMonitor* monitor() { return monitor_.get(); } |
| ScopedTempDir* temp_dir() { return temp_dir_.get(); } |
| TestDelegate* delegate() { return delegate_; } |
| |
| private: |
| scoped_refptr<TestTaskRunner> task_runner_; |
| std::unique_ptr<ScopedTempDir> temp_dir_; |
| std::unique_ptr<CPUFreqMonitor> monitor_; |
| TestDelegate* delegate_; |
| }; |
| |
| TEST_F(CPUFreqMonitorTest, TestStart) { |
| InitBasicCPUInfo(); |
| monitor()->Start(); |
| ASSERT_TRUE(monitor()->IsEnabledForTesting()); |
| } |
| |
| TEST_F(CPUFreqMonitorTest, TestSample) { |
| // Vector of CPU ID to frequency. |
| std::vector<std::pair<unsigned int, unsigned int>> frequencies = {{0, 500}, |
| {4, 1000}}; |
| std::vector<unsigned int> cpu_ids; |
| for (auto& pair : frequencies) { |
| cpu_ids.push_back(pair.first); |
| } |
| delegate()->set_cpu_ids(cpu_ids); |
| |
| // Build some files with CPU frequency info in it to sample. |
| std::vector<std::pair<unsigned int, base::ScopedFD>> fds; |
| for (auto& pair : frequencies) { |
| std::string file_path = base::StringPrintf( |
| "%s/temp%d", temp_dir()->GetPath().value().c_str(), pair.first); |
| |
| // Uses raw file descriptors so we can build our ScopedFDs in the same loop. |
| int fd = open(file_path.c_str(), O_RDWR | O_CREAT | O_SYNC, |
| S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| ASSERT_FALSE(fd == -1); |
| |
| std::string str_freq = base::StringPrintf("%d\n", pair.second); |
| write(fd, str_freq.c_str(), str_freq.length()); |
| fds.emplace_back(std::make_pair(pair.first, base::ScopedFD(fd))); |
| } |
| |
| // This ensures we set it to enabled before sampling, otherwise the call to |
| // Sample() will end early. |
| CreateDefaultScalingCurFreqFiles(frequencies); |
| monitor()->Start(); |
| ASSERT_TRUE(monitor()->IsEnabledForTesting()); |
| |
| // Ensure that we run our undelayed posted task for Sample. |
| ASSERT_EQ(GetOrCreateTaskRunner()->RunNextTask(), 0); |
| // Run the new delayed task so we sample again. |
| ASSERT_TRUE(GetOrCreateTaskRunner()->RunNextTask() == |
| CPUFreqMonitor::kDefaultCPUFreqSampleIntervalMs); |
| |
| // Ensure that the values that we recorded agree with the frequencies above. |
| auto recorded_freqs = delegate()->recorded_freqs(); |
| ASSERT_EQ(recorded_freqs.size(), frequencies.size() * 2); |
| for (unsigned int i = 0; i < frequencies.size(); i++) { |
| auto freq_pair = frequencies[i]; |
| // We sampled twice, so the recording pairs should be equal. |
| auto recorded_pair_1 = recorded_freqs[i]; |
| auto recorded_pair_2 = recorded_freqs[i + 2]; |
| ASSERT_EQ(freq_pair.first, recorded_pair_1.first); |
| ASSERT_EQ(freq_pair.second, recorded_pair_1.second); |
| ASSERT_EQ(freq_pair.first, recorded_pair_2.first); |
| ASSERT_EQ(freq_pair.second, recorded_pair_2.second); |
| } |
| |
| // Test that calling Stop works, we shouldn't post any more tasks if Sample |
| // is called. |
| monitor()->Stop(); |
| // Clear out the first Sample task that's on deck, then try again to make sure |
| // no new task was posted. |
| ASSERT_TRUE(GetOrCreateTaskRunner()->RunNextTask() == |
| CPUFreqMonitor::kDefaultCPUFreqSampleIntervalMs); |
| ASSERT_EQ(GetOrCreateTaskRunner()->RunNextTask(), -1); |
| } |
| |
| TEST_F(CPUFreqMonitorTest, TestStartFail_TraceCategoryDisabled) { |
| delegate()->set_trace_category_enabled(false); |
| CreateDefaultScalingCurFreqFiles({{0, 1000}}); |
| monitor()->Start(); |
| ASSERT_FALSE(monitor()->IsEnabledForTesting()); |
| } |
| |
| TEST_F(CPUFreqMonitorTest, TestStartFail_NoScalingCurFreqFiles) { |
| monitor()->Start(); |
| ASSERT_FALSE(monitor()->IsEnabledForTesting()); |
| } |
| |
| TEST_F(CPUFreqMonitorTest, TestDelegate_GetCPUIds) { |
| delegate()->set_kernel_max_cpu(8); |
| std::vector<std::string> related_cpus = {"0 1 2 3\n", "4 5 6 7\n"}; |
| std::vector<unsigned int> clusters = {0, 0, 0, 0, 1, 1, 1, 1}; |
| |
| CreateRelatedCPUFiles(clusters, related_cpus); |
| |
| std::vector<unsigned int> cpu_ids; |
| delegate()->GetCPUIds(&cpu_ids); |
| EXPECT_EQ(cpu_ids.size(), 2U); |
| EXPECT_EQ(cpu_ids[0], 0U); |
| EXPECT_EQ(cpu_ids[1], 4U); |
| } |
| |
| TEST_F(CPUFreqMonitorTest, TestDelegate_GetCPUIds_FailReadingFallback) { |
| delegate()->set_kernel_max_cpu(8); |
| |
| std::vector<unsigned int> cpu_ids; |
| delegate()->GetCPUIds(&cpu_ids); |
| EXPECT_EQ(cpu_ids.size(), 1U); |
| EXPECT_EQ(cpu_ids[0], 0U); |
| } |
| |
| TEST_F(CPUFreqMonitorTest, TestMultipleStartStop) { |
| InitBasicCPUInfo(); |
| |
| monitor()->Start(); |
| ASSERT_TRUE(monitor()->IsEnabledForTesting()); |
| monitor()->Stop(); |
| ASSERT_FALSE(monitor()->IsEnabledForTesting()); |
| |
| monitor()->Start(); |
| ASSERT_TRUE(monitor()->IsEnabledForTesting()); |
| monitor()->Stop(); |
| ASSERT_FALSE(monitor()->IsEnabledForTesting()); |
| } |
| |
| TEST_F(CPUFreqMonitorTest, TestTraceLogEnableDisable) { |
| InitBasicCPUInfo(); |
| |
| monitor()->OnTraceLogEnabled(); |
| // OnTraceLogEnabled posts a task for Start. |
| GetOrCreateTaskRunner()->RunNextTask(); |
| ASSERT_TRUE(monitor()->IsEnabledForTesting()); |
| monitor()->OnTraceLogDisabled(); |
| ASSERT_FALSE(monitor()->IsEnabledForTesting()); |
| // We also need to clear out the task for Sample from the Start call. |
| GetOrCreateTaskRunner()->RunNextTask(); |
| |
| monitor()->OnTraceLogEnabled(); |
| GetOrCreateTaskRunner()->RunNextTask(); |
| ASSERT_TRUE(monitor()->IsEnabledForTesting()); |
| monitor()->OnTraceLogDisabled(); |
| ASSERT_FALSE(monitor()->IsEnabledForTesting()); |
| } |
| |
| } // namespace trace_event |
| } // namespace base |