// 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 #include #include "base/files/file_util.h" #include "base/files/scoped_file.h" #include "base/files/scoped_temp_dir.h" #include "base/memory/raw_ptr.h" #include "base/strings/stringprintf.h" #include "base/time/time.h" #include "testing/gtest/include/gtest/gtest.h" namespace base { namespace trace_event { class TestTaskRunner final : 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() override {} std::list> 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& 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>& recorded_freqs() { return recorded_freqs_; } // CPUFreqMonitorDelegate implementation: void GetCPUIds(std::vector* 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(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 CreateTaskRunner() override { return base::WrapRefCounted(new TestTaskRunner()); } private: // Maps CPU ID to frequency. std::vector> recorded_freqs_; std::vector 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(); ASSERT_TRUE(temp_dir_->CreateUniqueTempDir()); std::string base_path = temp_dir_->GetPath().value(); auto delegate = std::make_unique(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>& 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); } } void CreateRelatedCPUFiles(const std::vector& clusters, const std::vector& related_cpus) { for (unsigned int i = 0; i < clusters.size(); i++) { base::WriteFile(base::FilePath(delegate_->GetRelatedCPUsPathString(i)), related_cpus[clusters[i]]); } } void InitBasicCPUInfo() { std::vector> frequencies = { {0, 500}, {2, 1000}, {4, 800}, {6, 750}, }; std::vector 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( monitor_->GetOrCreateTaskRunner().get()); } CPUFreqMonitor* monitor() { return monitor_.get(); } ScopedTempDir* temp_dir() { return temp_dir_.get(); } TestDelegate* delegate() { return delegate_; } private: scoped_refptr task_runner_; std::unique_ptr temp_dir_; std::unique_ptr monitor_; raw_ptr delegate_; }; TEST_F(CPUFreqMonitorTest, TestStart) { InitBasicCPUInfo(); monitor()->Start(); ASSERT_TRUE(monitor()->IsEnabledForTesting()); } TEST_F(CPUFreqMonitorTest, TestSample) { // Vector of CPU ID to frequency. std::vector> frequencies = {{0, 500}, {4, 1000}}; std::vector 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> 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 related_cpus = {"0 1 2 3\n", "4 5 6 7\n"}; std::vector clusters = {0, 0, 0, 0, 1, 1, 1, 1}; CreateRelatedCPUFiles(clusters, related_cpus); std::vector 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 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