| // Copyright 2021 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 "components/power_metrics/resource_coalition_mac.h" |
| |
| #include "base/rand_util.h" |
| #include "components/power_metrics/energy_impact_mac.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| namespace power_metrics { |
| |
| namespace { |
| |
| constexpr mach_timebase_info_data_t kIntelTimebase = {1, 1}; |
| constexpr mach_timebase_info_data_t kM1Timebase = {125, 3}; |
| |
| constexpr EnergyImpactCoefficients kEnergyImpactCoefficients{ |
| .kcpu_wakeups = base::Microseconds(200).InSecondsF(), |
| .kqos_default = 1.0, |
| .kqos_background = 0.8, |
| .kqos_utility = 1.0, |
| .kqos_legacy = 1.0, |
| .kqos_user_initiated = 1.0, |
| .kqos_user_interactive = 1.0, |
| .kgpu_time = 2.5, |
| }; |
| |
| coalition_resource_usage GetTestCoalitionResourceUsage(uint32_t increment) { |
| coalition_resource_usage ret{ |
| .tasks_started = 1 + increment, |
| .tasks_exited = 2 + increment, |
| .time_nonempty = 3 + increment, |
| .cpu_time = 4 + increment, |
| .interrupt_wakeups = 5 + increment, |
| .platform_idle_wakeups = 6 + increment, |
| .bytesread = 7 + increment, |
| .byteswritten = 8 + increment, |
| .gpu_time = 9 + increment, |
| .cpu_time_billed_to_me = 10 + increment, |
| .cpu_time_billed_to_others = 11 + increment, |
| .energy = 12 + increment, |
| .logical_immediate_writes = 13 + increment, |
| .logical_deferred_writes = 14 + increment, |
| .logical_invalidated_writes = 15 + increment, |
| .logical_metadata_writes = 16 + increment, |
| .logical_immediate_writes_to_external = 17 + increment, |
| .logical_deferred_writes_to_external = 18 + increment, |
| .logical_invalidated_writes_to_external = 19 + increment, |
| .logical_metadata_writes_to_external = 20 + increment, |
| .energy_billed_to_me = 21 + increment, |
| .energy_billed_to_others = 22 + increment, |
| .cpu_ptime = 23 + increment, |
| .cpu_time_eqos_len = COALITION_NUM_THREAD_QOS_TYPES, |
| .cpu_instructions = 31 + increment, |
| .cpu_cycles = 32 + increment, |
| .fs_metadata_writes = 33 + increment, |
| .pm_writes = 34 + increment, |
| }; |
| |
| ret.cpu_time_eqos[THREAD_QOS_DEFAULT] = 24 + increment; |
| ret.cpu_time_eqos[THREAD_QOS_MAINTENANCE] = 25 + increment; |
| ret.cpu_time_eqos[THREAD_QOS_BACKGROUND] = 26 + increment; |
| ret.cpu_time_eqos[THREAD_QOS_UTILITY] = 27 + increment; |
| ret.cpu_time_eqos[THREAD_QOS_LEGACY] = 28 + increment; |
| ret.cpu_time_eqos[THREAD_QOS_USER_INITIATED] = 29 + increment; |
| ret.cpu_time_eqos[THREAD_QOS_USER_INTERACTIVE] = 30 + increment; |
| |
| return ret; |
| } |
| |
| void BurnCPU() { |
| base::TimeTicks begin = base::TimeTicks::Now(); |
| constexpr base::TimeDelta busy_time = base::Seconds(1); |
| [[maybe_unused]] volatile double number = 1; |
| while (base::TimeTicks::Now() < (begin + busy_time)) { |
| for (int i = 0; i < 10000; ++i) |
| number = number * base::RandDouble(); |
| } |
| } |
| |
| } // namespace |
| |
| TEST(ResourceCoalitionMacTest, Busy) { |
| absl::optional<uint64_t> coalition_id = |
| GetProcessCoalitionId(base::GetCurrentProcId()); |
| ASSERT_TRUE(coalition_id.has_value()); |
| |
| std::unique_ptr<coalition_resource_usage> begin = |
| GetCoalitionResourceUsage(coalition_id.value()); |
| BurnCPU(); |
| std::unique_ptr<coalition_resource_usage> end = |
| GetCoalitionResourceUsage(coalition_id.value()); |
| |
| ASSERT_TRUE(begin); |
| ASSERT_TRUE(end); |
| |
| // Waterfall suggests that `cpu_instructions` and `cpu_cycles` are not |
| // populated prior to macOS 10.15. |
| if (@available(macOS 10.15, *)) { |
| EXPECT_GT(end->cpu_instructions, begin->cpu_instructions); |
| EXPECT_GT(end->cpu_cycles, begin->cpu_cycles); |
| } else { |
| EXPECT_EQ(0u, begin->cpu_instructions); |
| EXPECT_EQ(0u, begin->cpu_cycles); |
| EXPECT_EQ(0u, end->cpu_instructions); |
| EXPECT_EQ(0u, end->cpu_cycles); |
| } |
| EXPECT_GT(end->cpu_time, begin->cpu_time); |
| } |
| |
| TEST(ResourceCoalitionMacTest, Difference) { |
| coalition_resource_usage left = |
| GetTestCoalitionResourceUsage(/* increment= */ 1); |
| coalition_resource_usage right = |
| GetTestCoalitionResourceUsage(/* increment= */ 0); |
| coalition_resource_usage diff = |
| GetCoalitionResourceUsageDifference(left, right); |
| |
| EXPECT_EQ(diff.tasks_started, 1U); |
| EXPECT_EQ(diff.tasks_exited, 1U); |
| EXPECT_EQ(diff.time_nonempty, 1U); |
| EXPECT_EQ(diff.cpu_time, 1U); |
| EXPECT_EQ(diff.interrupt_wakeups, 1U); |
| EXPECT_EQ(diff.platform_idle_wakeups, 1U); |
| EXPECT_EQ(diff.bytesread, 1U); |
| EXPECT_EQ(diff.byteswritten, 1U); |
| EXPECT_EQ(diff.gpu_time, 1U); |
| EXPECT_EQ(diff.cpu_time_billed_to_me, 1U); |
| EXPECT_EQ(diff.cpu_time_billed_to_others, 1U); |
| EXPECT_EQ(diff.energy, 1U); |
| EXPECT_EQ(diff.logical_immediate_writes, 1U); |
| EXPECT_EQ(diff.logical_deferred_writes, 1U); |
| EXPECT_EQ(diff.logical_invalidated_writes, 1U); |
| EXPECT_EQ(diff.logical_metadata_writes, 1U); |
| EXPECT_EQ(diff.logical_immediate_writes_to_external, 1U); |
| EXPECT_EQ(diff.logical_deferred_writes_to_external, 1U); |
| EXPECT_EQ(diff.logical_invalidated_writes_to_external, 1U); |
| EXPECT_EQ(diff.logical_metadata_writes_to_external, 1U); |
| EXPECT_EQ(diff.energy_billed_to_me, 1U); |
| EXPECT_EQ(diff.energy_billed_to_others, 1U); |
| EXPECT_EQ(diff.cpu_ptime, 1U); |
| EXPECT_EQ(diff.cpu_time_eqos_len, |
| static_cast<uint64_t>(COALITION_NUM_THREAD_QOS_TYPES)); |
| |
| for (int i = 0; i < COALITION_NUM_THREAD_QOS_TYPES; ++i) |
| EXPECT_EQ(diff.cpu_time_eqos[i], 1U); |
| |
| EXPECT_EQ(diff.cpu_instructions, 1U); |
| EXPECT_EQ(diff.cpu_cycles, 1U); |
| EXPECT_EQ(diff.fs_metadata_writes, 1U); |
| EXPECT_EQ(diff.pm_writes, 1U); |
| } |
| |
| namespace { |
| |
| constexpr base::TimeDelta kIntervalDuration = base::Seconds(2.5); |
| |
| constexpr double kExpectedCPUUsagePerSecondPercent = 0.7; |
| constexpr double kExpectedGPUUsagePerSecondPercent = 0.3; |
| // Note: The following counters must have an integral value once multiplied by |
| // the interval length in seconds (2.5). |
| constexpr double kExpectedInterruptWakeUpPerSecond = 0.4; |
| constexpr double kExpectedPlatformIdleWakeUpPerSecond = 10; |
| constexpr double kExpectedBytesReadPerSecond = 0.8; |
| constexpr double kExpectedBytesWrittenPerSecond = 1.6; |
| constexpr double kExpectedPowerNW = 10000.0; |
| // This number will be multiplied by the int value associated with a QoS level |
| // to compute the expected time spent in this QoS level. E.g. |
| // |QoSLevels::kUtility == 3| so the time spent in the utility QoS state will |
| // be set to 3 * 0.1 = 30%. |
| constexpr double kExpectedQoSTimeBucketIdMultiplier = 0.1; |
| |
| // Scales a time given in ns to mach_time in |timebase|. |
| uint64_t NsScaleToTimebase(const mach_timebase_info_data_t& timebase, |
| int64_t time_ns) { |
| return time_ns * timebase.denom / timebase.numer; |
| } |
| |
| // Returns test data with all time quantities scaled to the given time base. |
| std::unique_ptr<coalition_resource_usage> GetCoalitionResourceUsageRateTestData( |
| const mach_timebase_info_data_t& timebase) { |
| std::unique_ptr<coalition_resource_usage> test_data = |
| std::make_unique<coalition_resource_usage>(); |
| |
| // Scales a time given in ns to mach_time in |timebase|. |
| auto scale_to_timebase = [&timebase](double time_ns) -> int64_t { |
| return NsScaleToTimebase(timebase, time_ns); |
| }; |
| |
| test_data->cpu_time = scale_to_timebase(kExpectedCPUUsagePerSecondPercent * |
| kIntervalDuration.InNanoseconds()); |
| test_data->interrupt_wakeups = |
| kExpectedInterruptWakeUpPerSecond * kIntervalDuration.InSecondsF(); |
| test_data->platform_idle_wakeups = |
| kExpectedPlatformIdleWakeUpPerSecond * kIntervalDuration.InSecondsF(); |
| test_data->bytesread = |
| kExpectedBytesReadPerSecond * kIntervalDuration.InSecondsF(); |
| test_data->byteswritten = |
| kExpectedBytesWrittenPerSecond * kIntervalDuration.InSecondsF(); |
| test_data->gpu_time = scale_to_timebase(kExpectedGPUUsagePerSecondPercent * |
| kIntervalDuration.InNanoseconds()); |
| test_data->energy = kExpectedPowerNW * kIntervalDuration.InSecondsF(); |
| for (int i = 0; i < COALITION_NUM_THREAD_QOS_TYPES; ++i) { |
| test_data->cpu_time_eqos[i] = |
| scale_to_timebase(i * kExpectedQoSTimeBucketIdMultiplier * |
| kIntervalDuration.InNanoseconds()); |
| } |
| test_data->cpu_time_eqos_len = COALITION_NUM_THREAD_QOS_TYPES; |
| |
| return test_data; |
| } |
| |
| } // namespace |
| |
| TEST(ResourceCoalitionMacTest, GetDataRate_NoEnergyImpact_Intel) { |
| // Keep the initial data zero initialized. |
| std::unique_ptr<coalition_resource_usage> t0_data = |
| std::make_unique<coalition_resource_usage>(); |
| std::unique_ptr<coalition_resource_usage> t1_data = |
| GetCoalitionResourceUsageRateTestData(kIntelTimebase); |
| |
| auto rate = GetCoalitionResourceUsageRate( |
| *t0_data, *t1_data, kIntervalDuration, kIntelTimebase, absl::nullopt); |
| ASSERT_TRUE(rate); |
| EXPECT_EQ(kExpectedCPUUsagePerSecondPercent, rate->cpu_time_per_second); |
| EXPECT_EQ(kExpectedInterruptWakeUpPerSecond, |
| rate->interrupt_wakeups_per_second); |
| EXPECT_EQ(kExpectedPlatformIdleWakeUpPerSecond, |
| rate->platform_idle_wakeups_per_second); |
| EXPECT_EQ(kExpectedBytesReadPerSecond, rate->bytesread_per_second); |
| EXPECT_EQ(kExpectedBytesWrittenPerSecond, rate->byteswritten_per_second); |
| EXPECT_EQ(kExpectedGPUUsagePerSecondPercent, rate->gpu_time_per_second); |
| EXPECT_FALSE(rate->energy_impact_per_second.has_value()); |
| EXPECT_EQ(kExpectedPowerNW, rate->power_nw); |
| |
| for (int i = 0; i < COALITION_NUM_THREAD_QOS_TYPES; ++i) { |
| EXPECT_DOUBLE_EQ(i * kExpectedQoSTimeBucketIdMultiplier, |
| rate->qos_time_per_second[i]); |
| } |
| } |
| |
| TEST(ResourceCoalitionMacTest, GetDataRate_NoEnergyImpact_M1) { |
| // Keep the initial data zero initialized. |
| std::unique_ptr<coalition_resource_usage> t0_data = |
| std::make_unique<coalition_resource_usage>(); |
| std::unique_ptr<coalition_resource_usage> t1_data = |
| GetCoalitionResourceUsageRateTestData(kM1Timebase); |
| |
| auto rate = GetCoalitionResourceUsageRate( |
| *t0_data, *t1_data, kIntervalDuration, kM1Timebase, absl::nullopt); |
| ASSERT_TRUE(rate); |
| EXPECT_DOUBLE_EQ(kExpectedCPUUsagePerSecondPercent, |
| rate->cpu_time_per_second); |
| EXPECT_DOUBLE_EQ(kExpectedInterruptWakeUpPerSecond, |
| rate->interrupt_wakeups_per_second); |
| EXPECT_DOUBLE_EQ(kExpectedPlatformIdleWakeUpPerSecond, |
| rate->platform_idle_wakeups_per_second); |
| EXPECT_DOUBLE_EQ(kExpectedBytesReadPerSecond, rate->bytesread_per_second); |
| EXPECT_DOUBLE_EQ(kExpectedBytesWrittenPerSecond, |
| rate->byteswritten_per_second); |
| EXPECT_DOUBLE_EQ(kExpectedGPUUsagePerSecondPercent, |
| rate->gpu_time_per_second); |
| EXPECT_FALSE(rate->energy_impact_per_second.has_value()); |
| EXPECT_DOUBLE_EQ(kExpectedPowerNW, rate->power_nw); |
| |
| for (int i = 0; i < COALITION_NUM_THREAD_QOS_TYPES; ++i) { |
| EXPECT_DOUBLE_EQ(i * kExpectedQoSTimeBucketIdMultiplier, |
| rate->qos_time_per_second[i]); |
| } |
| } |
| |
| TEST(ResourceCoalitionMacTest, GetDataRate_WithEnergyImpact_Intel) { |
| std::unique_ptr<coalition_resource_usage> t0_data = |
| std::make_unique<coalition_resource_usage>(); |
| std::unique_ptr<coalition_resource_usage> t1_data = |
| GetCoalitionResourceUsageRateTestData(kIntelTimebase); |
| |
| auto rate = |
| GetCoalitionResourceUsageRate(*t0_data, *t1_data, kIntervalDuration, |
| kIntelTimebase, kEnergyImpactCoefficients); |
| ASSERT_TRUE(rate); |
| EXPECT_EQ(kExpectedCPUUsagePerSecondPercent, rate->cpu_time_per_second); |
| EXPECT_EQ(kExpectedInterruptWakeUpPerSecond, |
| rate->interrupt_wakeups_per_second); |
| EXPECT_EQ(kExpectedPlatformIdleWakeUpPerSecond, |
| rate->platform_idle_wakeups_per_second); |
| EXPECT_EQ(kExpectedBytesReadPerSecond, rate->bytesread_per_second); |
| EXPECT_EQ(kExpectedBytesWrittenPerSecond, rate->byteswritten_per_second); |
| EXPECT_EQ(kExpectedGPUUsagePerSecondPercent, rate->gpu_time_per_second); |
| ASSERT_TRUE(rate->energy_impact_per_second.has_value()); |
| EXPECT_EQ(271.2, rate->energy_impact_per_second.value()); |
| EXPECT_FLOAT_EQ(kExpectedPowerNW, rate->power_nw); |
| |
| for (int i = 0; i < COALITION_NUM_THREAD_QOS_TYPES; ++i) { |
| EXPECT_DOUBLE_EQ(i * kExpectedQoSTimeBucketIdMultiplier, |
| rate->qos_time_per_second[i]); |
| } |
| } |
| |
| TEST(ResourceCoalitionMacTest, GetDataRate_WithEnergyImpact_M1) { |
| std::unique_ptr<coalition_resource_usage> t0_data = |
| std::make_unique<coalition_resource_usage>(); |
| std::unique_ptr<coalition_resource_usage> t1_data = |
| GetCoalitionResourceUsageRateTestData(kM1Timebase); |
| |
| auto rate = |
| GetCoalitionResourceUsageRate(*t0_data, *t1_data, kIntervalDuration, |
| kM1Timebase, kEnergyImpactCoefficients); |
| ASSERT_TRUE(rate); |
| EXPECT_EQ(kExpectedCPUUsagePerSecondPercent, rate->cpu_time_per_second); |
| EXPECT_EQ(kExpectedInterruptWakeUpPerSecond, |
| rate->interrupt_wakeups_per_second); |
| EXPECT_EQ(kExpectedPlatformIdleWakeUpPerSecond, |
| rate->platform_idle_wakeups_per_second); |
| EXPECT_EQ(kExpectedBytesReadPerSecond, rate->bytesread_per_second); |
| EXPECT_EQ(kExpectedBytesWrittenPerSecond, rate->byteswritten_per_second); |
| EXPECT_EQ(kExpectedGPUUsagePerSecondPercent, rate->gpu_time_per_second); |
| ASSERT_TRUE(rate->energy_impact_per_second.has_value()); |
| EXPECT_EQ(271.2, rate->energy_impact_per_second.value()); |
| EXPECT_FLOAT_EQ(kExpectedPowerNW, rate->power_nw); |
| |
| for (int i = 0; i < COALITION_NUM_THREAD_QOS_TYPES; ++i) { |
| EXPECT_DOUBLE_EQ(i * kExpectedQoSTimeBucketIdMultiplier, |
| rate->qos_time_per_second[i]); |
| } |
| } |
| |
| namespace { |
| |
| bool DataOverflowInvalidatesDiffImpl( |
| std::unique_ptr<coalition_resource_usage> t0, |
| std::unique_ptr<coalition_resource_usage> t1, |
| uint64_t* field_to_overflow) { |
| // Initialize all fields to a non zero value. |
| ::memset(t0.get(), 1000, sizeof(coalition_resource_usage)); |
| ::memset(t1.get(), 1000, sizeof(coalition_resource_usage)); |
| *field_to_overflow = 0; |
| t1->cpu_time_eqos_len = COALITION_NUM_THREAD_QOS_TYPES; |
| return !GetCoalitionResourceUsageRate(*t0, *t1, kIntervalDuration, |
| kIntelTimebase, absl::nullopt) |
| .has_value(); |
| } |
| |
| bool DataOverflowInvalidatesDiff( |
| uint64_t coalition_resource_usage::*member_ptr) { |
| std::unique_ptr<coalition_resource_usage> t0_data = |
| std::make_unique<coalition_resource_usage>(); |
| std::unique_ptr<coalition_resource_usage> t1_data = |
| std::make_unique<coalition_resource_usage>(); |
| auto* ptr = &(t1_data.get()->*member_ptr); |
| return DataOverflowInvalidatesDiffImpl(std::move(t0_data), std::move(t1_data), |
| ptr); |
| } |
| |
| bool DataOverflowInvalidatesDiff( |
| uint64_t ( |
| coalition_resource_usage::*member_ptr)[COALITION_NUM_THREAD_QOS_TYPES], |
| int index_to_check) { |
| std::unique_ptr<coalition_resource_usage> t0_data = |
| std::make_unique<coalition_resource_usage>(); |
| std::unique_ptr<coalition_resource_usage> t1_data = |
| std::make_unique<coalition_resource_usage>(); |
| auto* ptr = &(t1_data.get()->*member_ptr)[index_to_check]; |
| return DataOverflowInvalidatesDiffImpl(std::move(t0_data), std::move(t1_data), |
| ptr); |
| } |
| |
| } // namespace |
| |
| // If one of these tests fails then it means that overflows on a newly tracked |
| // coalition field aren't tracked properly in GetCoalitionResourceUsageRate(). |
| TEST(ResourceCoalitionTests, Overflows) { |
| EXPECT_FALSE(DataOverflowInvalidatesDiff( |
| &coalition_resource_usage::energy_billed_to_me)); |
| EXPECT_FALSE( |
| DataOverflowInvalidatesDiff(&coalition_resource_usage::tasks_started)); |
| EXPECT_FALSE( |
| DataOverflowInvalidatesDiff(&coalition_resource_usage::tasks_exited)); |
| EXPECT_FALSE( |
| DataOverflowInvalidatesDiff(&coalition_resource_usage::time_nonempty)); |
| EXPECT_TRUE(DataOverflowInvalidatesDiff(&coalition_resource_usage::cpu_time)); |
| EXPECT_TRUE(DataOverflowInvalidatesDiff( |
| &coalition_resource_usage::interrupt_wakeups)); |
| EXPECT_TRUE(DataOverflowInvalidatesDiff( |
| &coalition_resource_usage::platform_idle_wakeups)); |
| EXPECT_TRUE( |
| DataOverflowInvalidatesDiff(&coalition_resource_usage::bytesread)); |
| EXPECT_TRUE( |
| DataOverflowInvalidatesDiff(&coalition_resource_usage::byteswritten)); |
| EXPECT_TRUE(DataOverflowInvalidatesDiff(&coalition_resource_usage::gpu_time)); |
| EXPECT_FALSE(DataOverflowInvalidatesDiff( |
| &coalition_resource_usage::cpu_time_billed_to_me)); |
| EXPECT_FALSE(DataOverflowInvalidatesDiff( |
| &coalition_resource_usage::cpu_time_billed_to_others)); |
| EXPECT_TRUE(DataOverflowInvalidatesDiff(&coalition_resource_usage::energy)); |
| EXPECT_FALSE(DataOverflowInvalidatesDiff( |
| &coalition_resource_usage::logical_immediate_writes)); |
| EXPECT_FALSE(DataOverflowInvalidatesDiff( |
| &coalition_resource_usage::logical_deferred_writes)); |
| EXPECT_FALSE(DataOverflowInvalidatesDiff( |
| &coalition_resource_usage::logical_invalidated_writes)); |
| EXPECT_FALSE(DataOverflowInvalidatesDiff( |
| &coalition_resource_usage::logical_metadata_writes)); |
| EXPECT_FALSE(DataOverflowInvalidatesDiff( |
| &coalition_resource_usage::logical_immediate_writes_to_external)); |
| EXPECT_FALSE(DataOverflowInvalidatesDiff( |
| &coalition_resource_usage::logical_deferred_writes_to_external)); |
| EXPECT_FALSE(DataOverflowInvalidatesDiff( |
| &coalition_resource_usage::logical_invalidated_writes_to_external)); |
| EXPECT_FALSE(DataOverflowInvalidatesDiff( |
| &coalition_resource_usage::logical_metadata_writes_to_external)); |
| EXPECT_FALSE(DataOverflowInvalidatesDiff( |
| &coalition_resource_usage::energy_billed_to_me)); |
| EXPECT_FALSE(DataOverflowInvalidatesDiff( |
| &coalition_resource_usage::energy_billed_to_others)); |
| EXPECT_FALSE( |
| DataOverflowInvalidatesDiff(&coalition_resource_usage::cpu_ptime)); |
| for (int i = 0; i < COALITION_NUM_THREAD_QOS_TYPES; ++i) { |
| EXPECT_TRUE(DataOverflowInvalidatesDiff( |
| &coalition_resource_usage::cpu_time_eqos, i)); |
| } |
| EXPECT_FALSE( |
| DataOverflowInvalidatesDiff(&coalition_resource_usage::cpu_instructions)); |
| EXPECT_FALSE( |
| DataOverflowInvalidatesDiff(&coalition_resource_usage::cpu_cycles)); |
| EXPECT_FALSE(DataOverflowInvalidatesDiff( |
| &coalition_resource_usage::fs_metadata_writes)); |
| EXPECT_FALSE( |
| DataOverflowInvalidatesDiff(&coalition_resource_usage::pm_writes)); |
| } |
| |
| } // namespace power_metrics |