blob: bf26963cd1cdc66fd37d0d57a23dc1cd79856acd [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/390223051): Remove C-library calls to fix the errors.
#pragma allow_unsafe_libc_calls
#endif
#include "components/power_metrics/system_power_monitor.h"
#include "base/memory/raw_ptr.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace power_metrics {
class FakeProvider : public EnergyMetricsProvider {
public:
void set_metrics(EnergyMetrics metrics) { metrics_ = metrics; }
std::optional<EnergyMetrics> CaptureMetrics() override { return metrics_; }
private:
std::optional<EnergyMetrics> metrics_;
};
class FakeDelegate : public SystemPowerMonitorDelegate {
public:
void set_trace_category_enabled(bool enabled) {
trace_category_enabled_ = enabled;
}
void RecordSystemPower(const char* category,
base::TimeTicks timestamp,
int64_t power) override {
timestamp_ = timestamp;
if (strcmp(category, "Package Power (mW)") == 0) {
system_power_.package_nanojoules = static_cast<uint64_t>(power);
} else if (strcmp(category, "CPU Power (mW)") == 0) {
system_power_.cpu_nanojoules = static_cast<uint64_t>(power);
} else if (strcmp(category, "iGPU Power (mW)") == 0) {
system_power_.gpu_nanojoules = static_cast<uint64_t>(power);
} else if (strcmp(category, "DRAM Power (mW)") == 0) {
system_power_.dram_nanojoules = static_cast<uint64_t>(power);
} else if (strcmp(category, "Psys Power (mW)") == 0) {
system_power_.psys_nanojoules = static_cast<uint64_t>(power);
} else if (strcmp(category, "VDDCR VDD (mW)") == 0) {
system_power_.vdd_nanojoules = static_cast<uint64_t>(power);
} else if (strcmp(category, "VDDCR SOC (mW)") == 0) {
system_power_.soc_nanojoules = static_cast<uint64_t>(power);
} else if (strcmp(category, "Current Socket (mW)") == 0) {
system_power_.socket_nanojoules = static_cast<uint64_t>(power);
} else if (strcmp(category, "APU Power (mW)") == 0) {
system_power_.apu_nanojoules = static_cast<uint64_t>(power);
}
}
bool IsTraceCategoryEnabled() const override {
return trace_category_enabled_;
}
EnergyMetricsProvider::EnergyMetrics& SystemPower() { return system_power_; }
base::TimeTicks timestamp() { return timestamp_; }
private:
// We use EnergyMetrics to save recorded power data in milliwatts for
// simplicity.
EnergyMetricsProvider::EnergyMetrics system_power_;
base::TimeTicks timestamp_;
bool trace_category_enabled_{true};
};
class SystemPowerMonitorHelperTest : public testing::Test {
public:
SystemPowerMonitorHelperTest()
: task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
void SetUp() override {
auto provider = std::make_unique<FakeProvider>();
provider_ = provider.get();
auto delegate = std::make_unique<FakeDelegate>();
delegate_ = delegate.get();
helper_ = std::make_unique<SystemPowerMonitorHelper>(std::move(provider),
std::move(delegate));
}
void TearDown() override { helper_.reset(); }
base::test::TaskEnvironment& task_environment() { return task_environment_; }
SystemPowerMonitorHelper* helper() { return helper_.get(); }
FakeDelegate* delegate() { return delegate_.get(); }
FakeProvider* provider() { return provider_.get(); }
protected:
base::test::TaskEnvironment task_environment_;
private:
std::unique_ptr<SystemPowerMonitorHelper> helper_;
raw_ptr<FakeDelegate, DanglingUntriaged> delegate_;
raw_ptr<FakeProvider, DanglingUntriaged> provider_;
};
class SystemPowerMonitorTest : public testing::Test {
public:
SystemPowerMonitorTest() : task_environment_() {}
void SetUp() override {
auto provider = std::make_unique<FakeProvider>();
// Assign a valid metric to provider, so the timer can start successfully.
provider->set_metrics({1llu});
monitor_.reset(new SystemPowerMonitor(std::move(provider),
std::make_unique<FakeDelegate>()));
}
void TearDown() override { monitor_.reset(); }
SystemPowerMonitor* monitor() { return monitor_.get(); }
base::SequenceBound<SystemPowerMonitorHelper>* helper() {
return monitor_->GetHelperForTesting();
}
protected:
base::test::TaskEnvironment task_environment_;
private:
std::unique_ptr<SystemPowerMonitor> monitor_;
};
TEST_F(SystemPowerMonitorHelperTest, MonitorHelperStartStop) {
provider()->set_metrics({1llu});
helper()->Start();
ASSERT_TRUE(helper()->IsTimerRunningForTesting());
helper()->Stop();
ASSERT_FALSE(helper()->IsTimerRunningForTesting());
helper()->Start();
ASSERT_TRUE(helper()->IsTimerRunningForTesting());
helper()->Stop();
ASSERT_FALSE(helper()->IsTimerRunningForTesting());
}
TEST_F(SystemPowerMonitorHelperTest, TimerStartFailed_InvalidSample) {
// We haven't set metrics for provider, so monitor gets an
// std::nullopt sample at the beginning and it will not start.
helper()->Start();
ASSERT_FALSE(helper()->IsTimerRunningForTesting());
}
TEST_F(SystemPowerMonitorHelperTest, TimerStartFailed_MetricsAllZero) {
// If the metrics are all 0, we determine that there is no valid metric
// provided, so monitor will not start.
provider()->set_metrics({});
helper()->Start();
ASSERT_FALSE(helper()->IsTimerRunningForTesting());
}
TEST_F(SystemPowerMonitorHelperTest, TraceCategoryEnableDisable) {
provider()->set_metrics({1llu});
delegate()->set_trace_category_enabled(false);
ASSERT_FALSE(delegate()->IsTraceCategoryEnabled());
helper()->Start();
ASSERT_FALSE(helper()->IsTimerRunningForTesting());
delegate()->set_trace_category_enabled(true);
ASSERT_TRUE(delegate()->IsTraceCategoryEnabled());
helper()->Start();
ASSERT_TRUE(helper()->IsTimerRunningForTesting());
}
TEST_F(SystemPowerMonitorHelperTest, TestSample) {
EnergyMetricsProvider::EnergyMetrics sample1 = {
100000llu, 100000llu, 100000llu, 100000llu, 100000llu,
100000llu, 100000llu, 100000llu, 100000llu};
EnergyMetricsProvider::EnergyMetrics sample2 = {
200000llu, 300000llu, 400000llu, 500000llu, 600000llu,
700000llu, 800000llu, 900000llu, 1000000llu};
provider()->set_metrics(sample1);
helper()->Start();
ASSERT_TRUE(helper()->IsTimerRunningForTesting());
provider()->set_metrics(sample2);
task_environment().FastForwardBy(
SystemPowerMonitorHelper::kDefaultSampleInterval);
auto power = delegate()->SystemPower();
EXPECT_EQ(delegate()->timestamp() +
SystemPowerMonitorHelper::kDefaultSampleInterval,
task_environment().NowTicks());
EXPECT_EQ(
power.package_nanojoules,
(sample2.package_nanojoules - sample1.package_nanojoules) /
SystemPowerMonitorHelper::kDefaultSampleInterval.InMicroseconds());
EXPECT_EQ(
power.cpu_nanojoules,
(sample2.cpu_nanojoules - sample1.cpu_nanojoules) /
SystemPowerMonitorHelper::kDefaultSampleInterval.InMicroseconds());
EXPECT_EQ(
power.gpu_nanojoules,
(sample2.gpu_nanojoules - sample1.gpu_nanojoules) /
SystemPowerMonitorHelper::kDefaultSampleInterval.InMicroseconds());
EXPECT_EQ(
power.dram_nanojoules,
(sample2.dram_nanojoules - sample1.dram_nanojoules) /
SystemPowerMonitorHelper::kDefaultSampleInterval.InMicroseconds());
EXPECT_EQ(
power.psys_nanojoules,
(sample2.psys_nanojoules - sample1.psys_nanojoules) /
SystemPowerMonitorHelper::kDefaultSampleInterval.InMicroseconds());
EXPECT_EQ(
power.vdd_nanojoules,
(sample2.vdd_nanojoules - sample1.vdd_nanojoules) /
SystemPowerMonitorHelper::kDefaultSampleInterval.InMicroseconds());
EXPECT_EQ(
power.soc_nanojoules,
(sample2.soc_nanojoules - sample1.soc_nanojoules) /
SystemPowerMonitorHelper::kDefaultSampleInterval.InMicroseconds());
EXPECT_EQ(
power.socket_nanojoules,
(sample2.socket_nanojoules - sample1.socket_nanojoules) /
SystemPowerMonitorHelper::kDefaultSampleInterval.InMicroseconds());
EXPECT_EQ(
power.apu_nanojoules,
(sample2.apu_nanojoules - sample1.apu_nanojoules) /
SystemPowerMonitorHelper::kDefaultSampleInterval.InMicroseconds());
}
TEST_F(SystemPowerMonitorTest, TraceLogEnableDisable) {
ASSERT_NE(helper(), nullptr);
base::test::TestFuture<bool> future_enable;
monitor()->OnTraceLogEnabled();
helper()
->AsyncCall(&SystemPowerMonitorHelper::IsTimerRunningForTesting)
.Then(base::BindOnce(
[](base::OnceCallback<void(bool)> callback, bool is_running) {
std::move(callback).Run(is_running);
},
future_enable.GetCallback()));
EXPECT_TRUE(future_enable.Get());
base::test::TestFuture<bool> future_disable;
monitor()->OnTraceLogDisabled();
helper()
->AsyncCall(&SystemPowerMonitorHelper::IsTimerRunningForTesting)
.Then(base::BindOnce(
[](base::OnceCallback<void(bool)> callback, bool is_running) {
std::move(callback).Run(is_running);
},
future_disable.GetCallback()));
EXPECT_FALSE(future_disable.Get());
}
} // namespace power_metrics