0

[battery] Move computation of coalition resource usage rate to //components/power_metrics/.

In an upcoming CL, ProcessMonitor will no longer be involved in sampling
coalition resource usage. In preparation for that change, the code that
computes resource usage rate from 2 coalition resource usage samples is
moved out of //chrome/browser/performance_monitor/ to
//components/power_metrics/. Moving the code to a standalone function
also facilitates testing.

This CL does not change behavior, except that it use an optional field
for "energy impact" to make it possible not to report the value if it
couldn't be evaluated (previously, "0" was reported). This new behavior
is tested by
PowerMetricsReporterUnitTest.ReportResourceCoalitionHistograms_NoEnergyImpact.

Bug: 1293465
Change-Id: Ib478603a82d8c23e526336b3c44add66e2faeab3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3433768
Reviewed-by: Patrick Monette <pmonette@chromium.org>
Commit-Queue: Francois Pierre Doray <fdoray@chromium.org>
Cr-Commit-Position: refs/heads/main@{#966959}
This commit is contained in:
Francois Doray
2022-02-03 22:34:35 +00:00
committed by Chromium LUCI CQ
parent db07987317
commit dcdce27b4c
9 changed files with 466 additions and 497 deletions

@ -14,12 +14,17 @@
#include <stdint.h>
#include <memory>
#include <mach/mach_time.h>
#include "base/process/process_handle.h"
#include "base/time/time.h"
#include "components/power_metrics/resource_coalition_internal_types_mac.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace power_metrics {
struct EnergyImpactCoefficients;
// Returns the coalition id for the process identified by |pid| or nullopt if
// not available.
absl::optional<uint64_t> GetProcessCoalitionId(base::ProcessId pid);
@ -35,6 +40,31 @@ coalition_resource_usage GetCoalitionResourceUsageDifference(
const coalition_resource_usage& left,
const coalition_resource_usage& right);
// Struct that contains the rate of resource usage for a coalition.
struct CoalitionResourceUsageRate {
double cpu_time_per_second;
double interrupt_wakeups_per_second;
double platform_idle_wakeups_per_second;
double bytesread_per_second;
double byteswritten_per_second;
double gpu_time_per_second;
// Only makes sense on Intel macs, not computed on M1 macs.
absl::optional<double> energy_impact_per_second;
// Only available on M1 macs as of September 2021.
double power_nw;
double qos_time_per_second[THREAD_QOS_LAST];
};
// Returns rate of resource usage for a coalition, given the usage at
// the beginning and end of an interval and the duration of the interval.
absl::optional<CoalitionResourceUsageRate> GetCoalitionResourceUsageRate(
const coalition_resource_usage& begin,
const coalition_resource_usage& end,
base::TimeDelta interval_duration,
mach_timebase_info_data_t timebase,
absl::optional<EnergyImpactCoefficients> energy_impact_coefficients);
} // namespace power_metrics
#endif // COMPONENTS_PPOWER_METRICS_RESOURCE_COALITION_MAC_H_

@ -7,6 +7,8 @@
#include <libproc.h>
#include "base/check_op.h"
#include "components/power_metrics/energy_impact_mac.h"
#include "components/power_metrics/mach_time_mac.h"
extern "C" int coalition_info_resource_usage(
uint64_t cid,
@ -133,4 +135,75 @@ coalition_resource_usage GetCoalitionResourceUsageDifference(
return ret;
}
absl::optional<CoalitionResourceUsageRate> GetCoalitionResourceUsageRate(
const coalition_resource_usage& begin,
const coalition_resource_usage& end,
base::TimeDelta interval_duration,
mach_timebase_info_data_t timebase,
absl::optional<EnergyImpactCoefficients> energy_impact_coefficients) {
// Validate that |end| >= |begin|.
bool end_greater_or_equal_begin =
std::tie(end.cpu_time, end.interrupt_wakeups, end.platform_idle_wakeups,
end.bytesread, end.byteswritten, end.gpu_time, end.energy) >=
std::tie(begin.cpu_time, begin.interrupt_wakeups,
begin.platform_idle_wakeups, begin.bytesread, begin.byteswritten,
begin.gpu_time, begin.energy);
for (int i = 0; i < COALITION_NUM_THREAD_QOS_TYPES; ++i) {
if (end.cpu_time_eqos[i] < begin.cpu_time_eqos[i])
end_greater_or_equal_begin = false;
}
if (!end_greater_or_equal_begin)
return absl::nullopt;
auto get_rate_per_second = [&interval_duration](uint64_t begin,
uint64_t end) -> double {
DCHECK_GE(end, begin);
uint64_t diff = end - begin;
return diff / interval_duration.InSecondsF();
};
auto get_time_rate_per_second = [&interval_duration, &timebase](
uint64_t begin, uint64_t end) -> double {
DCHECK_GE(end, begin);
// Compute the delta in s, being careful to avoid truncation due to integral
// division.
double delta_sample_s =
power_metrics::MachTimeToNs(end - begin, timebase) /
static_cast<double>(base::Time::kNanosecondsPerSecond);
return delta_sample_s / interval_duration.InSecondsF();
};
CoalitionResourceUsageRate result;
result.cpu_time_per_second =
get_time_rate_per_second(begin.cpu_time, end.cpu_time);
result.interrupt_wakeups_per_second =
get_rate_per_second(begin.interrupt_wakeups, end.interrupt_wakeups);
result.platform_idle_wakeups_per_second = get_rate_per_second(
begin.platform_idle_wakeups, end.platform_idle_wakeups);
result.bytesread_per_second =
get_rate_per_second(begin.bytesread, end.bytesread);
result.byteswritten_per_second =
get_rate_per_second(begin.byteswritten, end.byteswritten);
result.gpu_time_per_second =
get_time_rate_per_second(begin.gpu_time, end.gpu_time);
result.power_nw = get_rate_per_second(begin.energy, end.energy);
for (int i = 0; i < COALITION_NUM_THREAD_QOS_TYPES; ++i) {
result.qos_time_per_second[i] =
get_time_rate_per_second(begin.cpu_time_eqos[i], end.cpu_time_eqos[i]);
}
if (energy_impact_coefficients.has_value()) {
result.energy_impact_per_second =
(ComputeEnergyImpactForResourceUsage(
end, energy_impact_coefficients.value(), timebase) -
ComputeEnergyImpactForResourceUsage(
begin, energy_impact_coefficients.value(), timebase)) /
interval_duration.InSecondsF();
}
return result;
}
} // power_metrics

@ -4,12 +4,28 @@
#include "components/power_metrics/resource_coalition_mac.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,
@ -98,4 +114,282 @@ TEST(ResourceCoalitionMacTest, Difference) {
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