Move code to compute macOS Energy Impact to //components/power_metrics.
This allows reuse by the code that records histograms in Chrome and the "power_sampler" tool in //tools/mac/power. Bug: 1254332 Change-Id: I9815af033848d4ea13ff39f724c68b2c66f41722 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3296547 Commit-Queue: Etienne Pierre-Doray <etiennep@chromium.org> Auto-Submit: François Doray <fdoray@chromium.org> Reviewed-by: Etienne Pierre-Doray <etiennep@chromium.org> Cr-Commit-Position: refs/heads/main@{#945466}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
60ac2fdec0
commit
eb603288d1
chrome/browser/performance_monitor
components
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include "base/files/file_path.h"
|
#include "base/files/file_path.h"
|
||||||
#include "base/time/time.h"
|
#include "base/time/time.h"
|
||||||
|
#include "components/power_metrics/energy_impact_mac.h"
|
||||||
#include "third_party/abseil-cpp/absl/types/optional.h"
|
#include "third_party/abseil-cpp/absl/types/optional.h"
|
||||||
|
|
||||||
// Forward declaration of the structure used internally to track resource usage.
|
// Forward declaration of the structure used internally to track resource usage.
|
||||||
@ -95,77 +96,17 @@ class ResourceCoalition {
|
|||||||
std::unique_ptr<coalition_resource_usage> recent_data_sample,
|
std::unique_ptr<coalition_resource_usage> recent_data_sample,
|
||||||
base::TimeDelta interval_length);
|
base::TimeDelta interval_length);
|
||||||
|
|
||||||
// The coefficients used to compute the EnergyImpact score from other resource
|
|
||||||
// data on Intel macs. Protected to allow exposing for testing.
|
|
||||||
// The order of the members mimics the order of keys in the plist files
|
|
||||||
// in /usr/share/pmenergy.
|
|
||||||
struct EnergyImpactCoefficients {
|
|
||||||
double kcpu_time;
|
|
||||||
|
|
||||||
// In units of seconds/event.
|
|
||||||
double kcpu_wakeups;
|
|
||||||
|
|
||||||
// Coefficients for different CPU levels.
|
|
||||||
// Strangely there's no coefficient for mainenance QOS.
|
|
||||||
double kqos_default;
|
|
||||||
double kqos_background;
|
|
||||||
double kqos_utility;
|
|
||||||
double kqos_legacy;
|
|
||||||
double kqos_user_initiated;
|
|
||||||
double kqos_user_interactive;
|
|
||||||
|
|
||||||
double kdiskio_bytesread;
|
|
||||||
double kdiskio_byteswritten;
|
|
||||||
|
|
||||||
double kgpu_time;
|
|
||||||
|
|
||||||
double knetwork_recv_bytes;
|
|
||||||
double knetwork_recv_packets;
|
|
||||||
double knetwork_sent_bytes;
|
|
||||||
double knetwork_sent_packets;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Reads the coefficients from the "energy_constants" sub-dictionary of
|
|
||||||
// the plist file at |plist_file|.
|
|
||||||
static absl::optional<EnergyImpactCoefficients>
|
|
||||||
ReadEnergyImpactCoefficientsFromPath(const base::FilePath& plist_file);
|
|
||||||
|
|
||||||
// Given a |directory| and a |board_id|, read the plist file for the board id
|
|
||||||
// from the directory, or if not available, read the default file.
|
|
||||||
// Returns true if either file can be loaded, false otherwise.
|
|
||||||
static absl::optional<EnergyImpactCoefficients>
|
|
||||||
ReadEnergyImpactOrDefaultForBoardId(const base::FilePath& directory,
|
|
||||||
const std::string& board_id);
|
|
||||||
|
|
||||||
// Computes the aggregate EnergyImpact score for the resource consumption
|
|
||||||
// data in |data_sample| with respect to the given |coefficients|.
|
|
||||||
// The Energy Impact (EI) score is referenced to CPU time, such that 10ms CPU
|
|
||||||
// time appears to be equivalent to 1 EI. The Activity Monitor presents EI
|
|
||||||
// rates to the user in units of 10ms/s of CPU time. This means a process that
|
|
||||||
// consumes 1000ms/s or 100% CPU, at default QOS, is rated 100 EI, making the
|
|
||||||
// two units somewhat relatable.
|
|
||||||
// Note that this only has relevance on Intel architecture, as it looks like
|
|
||||||
// on M1 architecture macOS implements more granular, and hopefully more
|
|
||||||
// accurate, energy metering on the fly.
|
|
||||||
double ComputeEnergyImpactForCoalitionUsage(
|
|
||||||
const EnergyImpactCoefficients& coefficients,
|
|
||||||
const coalition_resource_usage& data_sample);
|
|
||||||
|
|
||||||
// This appears to work for Intel Macs only.
|
|
||||||
static absl::optional<std::string> MaybeGetBoardIdForThisMachine();
|
|
||||||
|
|
||||||
// Initialize or reset the EI coefficients for testing.
|
// Initialize or reset the EI coefficients for testing.
|
||||||
void SetEnergyImpactCoefficientsForTesting(
|
void SetEnergyImpactCoefficientsForTesting(
|
||||||
const absl::optional<EnergyImpactCoefficients>& coefficients);
|
const absl::optional<power_metrics::EnergyImpactCoefficients>&
|
||||||
|
coefficients);
|
||||||
|
|
||||||
// Override the machine time base for testing.
|
// Override the machine time base for testing.
|
||||||
void SetMachTimebaseForTesting(
|
void SetMachTimebaseForTesting(
|
||||||
const mach_timebase_info_data_t& mach_timebase);
|
const mach_timebase_info_data_t& mach_timebase);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void EnsureEnergyImpactCoefficientsIfAvailable();
|
|
||||||
void SetCoalitionId(absl::optional<uint64_t> coalition_id);
|
void SetCoalitionId(absl::optional<uint64_t> coalition_id);
|
||||||
uint64_t MachTimeToNs(uint64_t mach_time);
|
|
||||||
|
|
||||||
// Computes the diff between two coalition_resource_usage objects and stores
|
// Computes the diff between two coalition_resource_usage objects and stores
|
||||||
// the per-second change rate for each field in a ResourceCoalition::Data
|
// the per-second change rate for each field in a ResourceCoalition::Data
|
||||||
@ -189,16 +130,14 @@ class ResourceCoalition {
|
|||||||
// mach_absolute_time units to ns.
|
// mach_absolute_time units to ns.
|
||||||
mach_timebase_info_data_t mach_timebase_;
|
mach_timebase_info_data_t mach_timebase_;
|
||||||
|
|
||||||
|
// Coefficients to compute Energy Impact from a `coalition_resource_usage`.
|
||||||
|
absl::optional<power_metrics::EnergyImpactCoefficients>
|
||||||
|
energy_impact_coefficients_;
|
||||||
|
|
||||||
// The data sample collected during the last call to GetDataDiff or since
|
// The data sample collected during the last call to GetDataDiff or since
|
||||||
// creating this object.
|
// creating this object.
|
||||||
std::unique_ptr<coalition_resource_usage> last_data_sample_;
|
std::unique_ptr<coalition_resource_usage> last_data_sample_;
|
||||||
|
|
||||||
// True if an attempt has been made to initialize the EI coefficients.
|
|
||||||
bool energy_impact_coefficients_initialized_ = false;
|
|
||||||
|
|
||||||
// The EI coefficients for this machine (or defaults), if available.
|
|
||||||
absl::optional<EnergyImpactCoefficients> energy_impact_coefficients_;
|
|
||||||
|
|
||||||
// The timestamp associated with |last_data_sample_|.
|
// The timestamp associated with |last_data_sample_|.
|
||||||
base::TimeTicks last_data_sample_timestamp_;
|
base::TimeTicks last_data_sample_timestamp_;
|
||||||
};
|
};
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
#include "base/process/process_handle.h"
|
#include "base/process/process_handle.h"
|
||||||
#include "base/strings/sys_string_conversions.h"
|
#include "base/strings/sys_string_conversions.h"
|
||||||
#include "base/time/time.h"
|
#include "base/time/time.h"
|
||||||
|
#include "components/power_metrics/energy_impact_mac.h"
|
||||||
|
#include "components/power_metrics/mach_time_mac.h"
|
||||||
#include "components/power_metrics/resource_coalition_mac.h"
|
#include "components/power_metrics/resource_coalition_mac.h"
|
||||||
|
|
||||||
namespace performance_monitor {
|
namespace performance_monitor {
|
||||||
@ -112,18 +114,6 @@ absl::optional<uint64_t> GetCurrentCoalitionId(
|
|||||||
return cid;
|
return cid;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSDictionary* MaybeGetDictionaryFromPath(const base::FilePath& path) {
|
|
||||||
// The folder where the energy coefficient plist files are stored.
|
|
||||||
NSString* plist_path_string = base::SysUTF8ToNSString(path.value().c_str());
|
|
||||||
return [NSDictionary dictionaryWithContentsOfFile:plist_path_string];
|
|
||||||
}
|
|
||||||
|
|
||||||
double GetNamedCoefficientOrZero(NSDictionary* dict, NSString* key) {
|
|
||||||
NSObject* value = [dict objectForKey:key];
|
|
||||||
NSNumber* num = base::mac::ObjCCast<NSNumber>(value);
|
|
||||||
return [num floatValue];
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
ResourceCoalition::DataRate::DataRate() = default;
|
ResourceCoalition::DataRate::DataRate() = default;
|
||||||
@ -132,18 +122,16 @@ ResourceCoalition::DataRate& ResourceCoalition::DataRate::operator=(
|
|||||||
const DataRate& other) = default;
|
const DataRate& other) = default;
|
||||||
ResourceCoalition::DataRate::~DataRate() = default;
|
ResourceCoalition::DataRate::~DataRate() = default;
|
||||||
|
|
||||||
ResourceCoalition::ResourceCoalition() {
|
ResourceCoalition::ResourceCoalition()
|
||||||
|
: mach_timebase_(power_metrics::GetSystemMachTimeBase()),
|
||||||
|
energy_impact_coefficients_(
|
||||||
|
power_metrics::ReadCoefficientsForCurrentMachineOrDefault()) {
|
||||||
CoalitionAvailability availability_details;
|
CoalitionAvailability availability_details;
|
||||||
SetCoalitionId(GetCurrentCoalitionId(&availability_details));
|
SetCoalitionId(GetCurrentCoalitionId(&availability_details));
|
||||||
base::UmaHistogramEnumeration(kCoalitionAvailabilityHistogram,
|
base::UmaHistogramEnumeration(kCoalitionAvailabilityHistogram,
|
||||||
availability_details);
|
availability_details);
|
||||||
|
|
||||||
// Initialize the machine timebase.
|
|
||||||
kern_return_t kr = mach_timebase_info(&mach_timebase_);
|
|
||||||
DCHECK_EQ(kr, KERN_SUCCESS);
|
|
||||||
DCHECK(mach_timebase_.numer);
|
|
||||||
DCHECK(mach_timebase_.denom);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourceCoalition::~ResourceCoalition() = default;
|
ResourceCoalition::~ResourceCoalition() = default;
|
||||||
|
|
||||||
absl::optional<ResourceCoalition::DataRate> ResourceCoalition::GetDataRate() {
|
absl::optional<ResourceCoalition::DataRate> ResourceCoalition::GetDataRate() {
|
||||||
@ -173,152 +161,9 @@ void ResourceCoalition::SetCoalitionIDToCurrentProcessIdForTesting() {
|
|||||||
power_metrics::GetProcessCoalitionId(base::GetCurrentProcId()));
|
power_metrics::GetProcessCoalitionId(base::GetCurrentProcId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
|
||||||
absl::optional<ResourceCoalition::EnergyImpactCoefficients>
|
|
||||||
ResourceCoalition::ReadEnergyImpactCoefficientsFromPath(
|
|
||||||
const base::FilePath& plist_file) {
|
|
||||||
@autoreleasepool {
|
|
||||||
NSDictionary* dict = MaybeGetDictionaryFromPath(plist_file);
|
|
||||||
if (!dict)
|
|
||||||
return absl::nullopt;
|
|
||||||
|
|
||||||
// We want the energy_constants sub-dictionary.
|
|
||||||
NSDictionary* energy_constants = [dict objectForKey:@"energy_constants"];
|
|
||||||
if (!energy_constants)
|
|
||||||
return absl::nullopt;
|
|
||||||
|
|
||||||
EnergyImpactCoefficients coefficients{};
|
|
||||||
coefficients.kcpu_time =
|
|
||||||
GetNamedCoefficientOrZero(energy_constants, @"kcpu_time");
|
|
||||||
coefficients.kcpu_wakeups =
|
|
||||||
GetNamedCoefficientOrZero(energy_constants, @"kcpu_wakeups");
|
|
||||||
|
|
||||||
coefficients.kqos_default =
|
|
||||||
GetNamedCoefficientOrZero(energy_constants, @"kqos_default");
|
|
||||||
coefficients.kqos_background =
|
|
||||||
GetNamedCoefficientOrZero(energy_constants, @"kqos_background");
|
|
||||||
coefficients.kqos_utility =
|
|
||||||
GetNamedCoefficientOrZero(energy_constants, @"kqos_utility");
|
|
||||||
coefficients.kqos_legacy =
|
|
||||||
GetNamedCoefficientOrZero(energy_constants, @"kqos_legacy");
|
|
||||||
coefficients.kqos_user_initiated =
|
|
||||||
GetNamedCoefficientOrZero(energy_constants, @"kqos_user_initiated");
|
|
||||||
coefficients.kqos_user_interactive =
|
|
||||||
GetNamedCoefficientOrZero(energy_constants, @"kqos_user_interactive");
|
|
||||||
|
|
||||||
coefficients.kdiskio_bytesread =
|
|
||||||
GetNamedCoefficientOrZero(energy_constants, @"kdiskio_bytesread");
|
|
||||||
coefficients.kdiskio_byteswritten =
|
|
||||||
GetNamedCoefficientOrZero(energy_constants, @"kdiskio_byteswritten");
|
|
||||||
|
|
||||||
coefficients.kgpu_time =
|
|
||||||
GetNamedCoefficientOrZero(energy_constants, @"kgpu_time");
|
|
||||||
|
|
||||||
coefficients.knetwork_recv_bytes =
|
|
||||||
GetNamedCoefficientOrZero(energy_constants, @"knetwork_recv_bytes");
|
|
||||||
coefficients.knetwork_recv_packets =
|
|
||||||
GetNamedCoefficientOrZero(energy_constants, @"knetwork_recv_packets");
|
|
||||||
coefficients.knetwork_sent_bytes =
|
|
||||||
GetNamedCoefficientOrZero(energy_constants, @"knetwork_sent_bytes");
|
|
||||||
coefficients.knetwork_sent_packets =
|
|
||||||
GetNamedCoefficientOrZero(energy_constants, @"knetwork_sent_packets");
|
|
||||||
|
|
||||||
return coefficients;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// static
|
|
||||||
absl::optional<ResourceCoalition::EnergyImpactCoefficients>
|
|
||||||
ResourceCoalition::ReadEnergyImpactOrDefaultForBoardId(
|
|
||||||
const base::FilePath& directory,
|
|
||||||
const std::string& board_id) {
|
|
||||||
auto coefficients = ReadEnergyImpactCoefficientsFromPath(
|
|
||||||
directory.Append(board_id).AddExtension(FILE_PATH_LITERAL("plist")));
|
|
||||||
if (coefficients.has_value())
|
|
||||||
return coefficients;
|
|
||||||
|
|
||||||
return ReadEnergyImpactCoefficientsFromPath(
|
|
||||||
directory.Append(FILE_PATH_LITERAL("default.plist")));
|
|
||||||
}
|
|
||||||
|
|
||||||
double ResourceCoalition::ComputeEnergyImpactForCoalitionUsage(
|
|
||||||
const EnergyImpactCoefficients& coefficients,
|
|
||||||
const coalition_resource_usage& data_sample) {
|
|
||||||
// TODO(https://crbug.com/1249536): The below coefficients are not used for
|
|
||||||
// now. Their units are unknown, and in the case of the network-related
|
|
||||||
// coefficients, it's not clear how to sample the data.
|
|
||||||
// coefficients.kdiskio_bytesread;
|
|
||||||
// coefficients.kdiskio_byteswritten;
|
|
||||||
// coefficients.knetwork_recv_bytes;
|
|
||||||
// coefficients.knetwork_recv_packets;
|
|
||||||
// coefficients.knetwork_sent_bytes;
|
|
||||||
// coefficients.knetwork_sent_packets;
|
|
||||||
|
|
||||||
// The cumulative CPU usage in |data_sample| is in units of ns, and
|
|
||||||
// |cpu_time_equivalent_ns| is computed in CPU ns up to the end of this
|
|
||||||
// function, where it's converted to units of EnergyImpact.
|
|
||||||
double cpu_time_equivalent_ns = 0.0;
|
|
||||||
|
|
||||||
// The kcpu_wakeups coefficient on disk is in seconds, but our intermediate
|
|
||||||
// result is in ns, so convert to ns on the fly.
|
|
||||||
cpu_time_equivalent_ns += coefficients.kcpu_wakeups *
|
|
||||||
base::Time::kNanosecondsPerSecond *
|
|
||||||
data_sample.platform_idle_wakeups;
|
|
||||||
|
|
||||||
// Presumably the kgpu_time coefficient has suitable units for the conversion
|
|
||||||
// of GPU time energy to CPU time energy. There is a fairly wide spread on
|
|
||||||
// this constant seen in /usr/share/pmenergy. On macOS 11.5.2 the spread is
|
|
||||||
// from 0 through 5.9.
|
|
||||||
cpu_time_equivalent_ns +=
|
|
||||||
coefficients.kgpu_time * MachTimeToNs(data_sample.gpu_time);
|
|
||||||
|
|
||||||
cpu_time_equivalent_ns +=
|
|
||||||
coefficients.kqos_background *
|
|
||||||
MachTimeToNs(data_sample.cpu_time_eqos[THREAD_QOS_BACKGROUND]);
|
|
||||||
cpu_time_equivalent_ns +=
|
|
||||||
coefficients.kqos_default *
|
|
||||||
MachTimeToNs(data_sample.cpu_time_eqos[THREAD_QOS_DEFAULT]);
|
|
||||||
cpu_time_equivalent_ns +=
|
|
||||||
coefficients.kqos_legacy *
|
|
||||||
MachTimeToNs(data_sample.cpu_time_eqos[THREAD_QOS_LEGACY]);
|
|
||||||
cpu_time_equivalent_ns +=
|
|
||||||
coefficients.kqos_user_initiated *
|
|
||||||
MachTimeToNs(data_sample.cpu_time_eqos[THREAD_QOS_USER_INITIATED]);
|
|
||||||
cpu_time_equivalent_ns +=
|
|
||||||
coefficients.kqos_user_interactive *
|
|
||||||
MachTimeToNs(data_sample.cpu_time_eqos[THREAD_QOS_USER_INTERACTIVE]);
|
|
||||||
cpu_time_equivalent_ns +=
|
|
||||||
coefficients.kqos_utility *
|
|
||||||
MachTimeToNs(data_sample.cpu_time_eqos[THREAD_QOS_UTILITY]);
|
|
||||||
|
|
||||||
// The conversion ratio for CPU time/EnergyImpact is ns/10ms
|
|
||||||
constexpr double kNsToEI = 1E-7;
|
|
||||||
return cpu_time_equivalent_ns * kNsToEI;
|
|
||||||
}
|
|
||||||
|
|
||||||
// static
|
|
||||||
absl::optional<std::string> ResourceCoalition::MaybeGetBoardIdForThisMachine() {
|
|
||||||
base::mac::ScopedIOObject<io_service_t> platform_expert(
|
|
||||||
IOServiceGetMatchingService(kIOMasterPortDefault,
|
|
||||||
IOServiceMatching("IOPlatformExpertDevice")));
|
|
||||||
if (!platform_expert)
|
|
||||||
return absl::nullopt;
|
|
||||||
|
|
||||||
// This is what libpmenergy is observed to do in order to retrieve the correct
|
|
||||||
// coefficients file for the local computer.
|
|
||||||
base::ScopedCFTypeRef<CFDataRef> board_id_data(
|
|
||||||
base::mac::CFCast<CFDataRef>(IORegistryEntryCreateCFProperty(
|
|
||||||
platform_expert, CFSTR("board-id"), kCFAllocatorDefault, 0)));
|
|
||||||
|
|
||||||
if (!board_id_data)
|
|
||||||
return absl::nullopt;
|
|
||||||
|
|
||||||
return reinterpret_cast<const char*>(CFDataGetBytePtr(board_id_data));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ResourceCoalition::SetEnergyImpactCoefficientsForTesting(
|
void ResourceCoalition::SetEnergyImpactCoefficientsForTesting(
|
||||||
const absl::optional<EnergyImpactCoefficients>& coefficients) {
|
const absl::optional<power_metrics::EnergyImpactCoefficients>&
|
||||||
energy_impact_coefficients_initialized_ = true;
|
coefficients) {
|
||||||
energy_impact_coefficients_ = coefficients;
|
energy_impact_coefficients_ = coefficients;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,20 +172,6 @@ void ResourceCoalition::SetMachTimebaseForTesting(
|
|||||||
mach_timebase_ = mach_timebase;
|
mach_timebase_ = mach_timebase;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ResourceCoalition::EnsureEnergyImpactCoefficientsIfAvailable() {
|
|
||||||
if (energy_impact_coefficients_initialized_)
|
|
||||||
return;
|
|
||||||
energy_impact_coefficients_initialized_ = true;
|
|
||||||
|
|
||||||
auto board_id = MaybeGetBoardIdForThisMachine();
|
|
||||||
if (!board_id.has_value())
|
|
||||||
return;
|
|
||||||
|
|
||||||
energy_impact_coefficients_ = ReadEnergyImpactOrDefaultForBoardId(
|
|
||||||
base::FilePath(FILE_PATH_LITERAL("/usr/share/pmenergy")),
|
|
||||||
board_id.value());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ResourceCoalition::SetCoalitionId(absl::optional<uint64_t> coalition_id) {
|
void ResourceCoalition::SetCoalitionId(absl::optional<uint64_t> coalition_id) {
|
||||||
coalition_id_ = coalition_id;
|
coalition_id_ = coalition_id;
|
||||||
if (coalition_id_.has_value()) {
|
if (coalition_id_.has_value()) {
|
||||||
@ -350,22 +181,11 @@ void ResourceCoalition::SetCoalitionId(absl::optional<uint64_t> coalition_id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t ResourceCoalition::MachTimeToNs(uint64_t mach_time) {
|
|
||||||
if (mach_timebase_.numer == mach_timebase_.denom)
|
|
||||||
return mach_time;
|
|
||||||
|
|
||||||
CHECK(
|
|
||||||
!__builtin_umulll_overflow(mach_time, mach_timebase_.numer, &mach_time));
|
|
||||||
return mach_time / mach_timebase_.denom;
|
|
||||||
}
|
|
||||||
|
|
||||||
absl::optional<ResourceCoalition::DataRate>
|
absl::optional<ResourceCoalition::DataRate>
|
||||||
ResourceCoalition::GetCoalitionDataDiff(
|
ResourceCoalition::GetCoalitionDataDiff(
|
||||||
const coalition_resource_usage& new_sample,
|
const coalition_resource_usage& new_sample,
|
||||||
const coalition_resource_usage& old_sample,
|
const coalition_resource_usage& old_sample,
|
||||||
base::TimeDelta interval_length) {
|
base::TimeDelta interval_length) {
|
||||||
DCHECK(energy_impact_coefficients_initialized_);
|
|
||||||
|
|
||||||
bool new_samples_exceeds_or_equals_old_ones =
|
bool new_samples_exceeds_or_equals_old_ones =
|
||||||
std::tie(new_sample.cpu_time, new_sample.interrupt_wakeups,
|
std::tie(new_sample.cpu_time, new_sample.interrupt_wakeups,
|
||||||
new_sample.platform_idle_wakeups, new_sample.bytesread,
|
new_sample.platform_idle_wakeups, new_sample.bytesread,
|
||||||
@ -402,7 +222,8 @@ ResourceCoalition::GetCoalitionDataDiff(
|
|||||||
// Compute the delta in s, being careful to avoid truncation due to integral
|
// Compute the delta in s, being careful to avoid truncation due to integral
|
||||||
// division.
|
// division.
|
||||||
double delta_sample_s =
|
double delta_sample_s =
|
||||||
self->MachTimeToNs(new_sample - old_sample) /
|
power_metrics::MachTimeToNs(new_sample - old_sample,
|
||||||
|
self->mach_timebase_) /
|
||||||
static_cast<double>(base::Time::kNanosecondsPerSecond);
|
static_cast<double>(base::Time::kNanosecondsPerSecond);
|
||||||
return delta_sample_s / interval_length.InSecondsF();
|
return delta_sample_s / interval_length.InSecondsF();
|
||||||
};
|
};
|
||||||
@ -427,12 +248,11 @@ ResourceCoalition::GetCoalitionDataDiff(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (energy_impact_coefficients_.has_value()) {
|
if (energy_impact_coefficients_.has_value()) {
|
||||||
const EnergyImpactCoefficients& coefficients =
|
|
||||||
energy_impact_coefficients_.value();
|
|
||||||
|
|
||||||
ret.energy_impact_per_second =
|
ret.energy_impact_per_second =
|
||||||
(ComputeEnergyImpactForCoalitionUsage(coefficients, new_sample) -
|
(power_metrics::ComputeEnergyImpactForResourceUsage(
|
||||||
ComputeEnergyImpactForCoalitionUsage(coefficients, old_sample)) /
|
new_sample, energy_impact_coefficients_.value(), mach_timebase_) -
|
||||||
|
power_metrics::ComputeEnergyImpactForResourceUsage(
|
||||||
|
old_sample, energy_impact_coefficients_.value(), mach_timebase_)) /
|
||||||
interval_length.InSecondsF();
|
interval_length.InSecondsF();
|
||||||
} else {
|
} else {
|
||||||
// TODO(siggi): Use something else here as sentinel?
|
// TODO(siggi): Use something else here as sentinel?
|
||||||
@ -445,8 +265,6 @@ ResourceCoalition::GetCoalitionDataDiff(
|
|||||||
absl::optional<ResourceCoalition::DataRate> ResourceCoalition::GetDataRateImpl(
|
absl::optional<ResourceCoalition::DataRate> ResourceCoalition::GetDataRateImpl(
|
||||||
std::unique_ptr<coalition_resource_usage> new_data_sample,
|
std::unique_ptr<coalition_resource_usage> new_data_sample,
|
||||||
base::TimeTicks now) {
|
base::TimeTicks now) {
|
||||||
// Make sure the EI coefficients are loaded, if possible.
|
|
||||||
EnsureEnergyImpactCoefficientsIfAvailable();
|
|
||||||
auto ret =
|
auto ret =
|
||||||
GetCoalitionDataDiff(*new_data_sample.get(), *last_data_sample_.get(),
|
GetCoalitionDataDiff(*new_data_sample.get(), *last_data_sample_.get(),
|
||||||
now - last_data_sample_timestamp_);
|
now - last_data_sample_timestamp_);
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#include "chrome/common/chrome_paths.h"
|
#include "chrome/common/chrome_paths.h"
|
||||||
#include "components/power_metrics/resource_coalition_internal_types_mac.h"
|
#include "components/power_metrics/resource_coalition_internal_types_mac.h"
|
||||||
#include "testing/gtest/include/gtest/gtest.h"
|
#include "testing/gtest/include/gtest/gtest.h"
|
||||||
|
#include "third_party/abseil-cpp/absl/types/optional.h"
|
||||||
|
|
||||||
namespace performance_monitor {
|
namespace performance_monitor {
|
||||||
|
|
||||||
@ -31,6 +32,8 @@ constexpr mach_timebase_info_data_t kIntelTimebase = {1, 1};
|
|||||||
// A sample (not definitive) timebase for M1.
|
// A sample (not definitive) timebase for M1.
|
||||||
constexpr mach_timebase_info_data_t kM1Timebase = {125, 3};
|
constexpr mach_timebase_info_data_t kM1Timebase = {125, 3};
|
||||||
|
|
||||||
|
using EnergyImpactCoefficients = power_metrics::EnergyImpactCoefficients;
|
||||||
|
|
||||||
// Initializes to no EI constants and the Intel timebase.
|
// Initializes to no EI constants and the Intel timebase.
|
||||||
class TestResourceCoalition : public ResourceCoalition {
|
class TestResourceCoalition : public ResourceCoalition {
|
||||||
public:
|
public:
|
||||||
@ -41,16 +44,9 @@ class TestResourceCoalition : public ResourceCoalition {
|
|||||||
// Expose as public for testing.
|
// Expose as public for testing.
|
||||||
using ResourceCoalition::SetCoalitionIDToCurrentProcessIdForTesting;
|
using ResourceCoalition::SetCoalitionIDToCurrentProcessIdForTesting;
|
||||||
using ResourceCoalition::GetDataRateFromFakeDataForTesting;
|
using ResourceCoalition::GetDataRateFromFakeDataForTesting;
|
||||||
using ResourceCoalition::EnergyImpactCoefficients;
|
|
||||||
using ResourceCoalition::ReadEnergyImpactCoefficientsFromPath;
|
|
||||||
using ResourceCoalition::ComputeEnergyImpactForCoalitionUsage;
|
|
||||||
using ResourceCoalition::ReadEnergyImpactOrDefaultForBoardId;
|
|
||||||
using ResourceCoalition::MaybeGetBoardIdForThisMachine;
|
|
||||||
using ResourceCoalition::SetEnergyImpactCoefficientsForTesting;
|
using ResourceCoalition::SetEnergyImpactCoefficientsForTesting;
|
||||||
using ResourceCoalition::SetMachTimebaseForTesting;
|
using ResourceCoalition::SetMachTimebaseForTesting;
|
||||||
};
|
};
|
||||||
using EnergyImpactCoefficients =
|
|
||||||
TestResourceCoalition::EnergyImpactCoefficients;
|
|
||||||
|
|
||||||
EnergyImpactCoefficients GetEnergyImpactTestCoefficients() {
|
EnergyImpactCoefficients GetEnergyImpactTestCoefficients() {
|
||||||
constexpr EnergyImpactCoefficients coefficients{
|
constexpr EnergyImpactCoefficients coefficients{
|
||||||
@ -422,221 +418,6 @@ TEST(ResourceCoalitionTests, Overflows) {
|
|||||||
coalition, &coalition_resource_usage::pm_writes));
|
coalition, &coalition_resource_usage::pm_writes));
|
||||||
}
|
}
|
||||||
|
|
||||||
base::FilePath GetTestDataPath() {
|
|
||||||
base::FilePath test_path;
|
|
||||||
EXPECT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_path));
|
|
||||||
return test_path.Append(FILE_PATH_LITERAL("performance_monitor"));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(ResourceCoalitionTests, ReadEnergyImpactCoefficientsFromPath) {
|
|
||||||
base::FilePath test_path = GetTestDataPath();
|
|
||||||
|
|
||||||
// Validate that attempting to read from a non-existent file fails.
|
|
||||||
auto coefficients =
|
|
||||||
TestResourceCoalition::ReadEnergyImpactCoefficientsFromPath(
|
|
||||||
test_path.Append(FILE_PATH_LITERAL("does-not-exist.plist")));
|
|
||||||
EXPECT_FALSE(coefficients.has_value());
|
|
||||||
|
|
||||||
// Validate that a well-formed file returns the expected coefficients.
|
|
||||||
coefficients = TestResourceCoalition::ReadEnergyImpactCoefficientsFromPath(
|
|
||||||
test_path.Append(FILE_PATH_LITERAL("test.plist")));
|
|
||||||
ASSERT_TRUE(coefficients.has_value());
|
|
||||||
|
|
||||||
const EnergyImpactCoefficients& value = coefficients.value();
|
|
||||||
EXPECT_FLOAT_EQ(value.kcpu_time, 1.23);
|
|
||||||
EXPECT_FLOAT_EQ(value.kdiskio_bytesread, 7.89);
|
|
||||||
EXPECT_FLOAT_EQ(value.kdiskio_byteswritten, 1.2345);
|
|
||||||
EXPECT_FLOAT_EQ(value.kgpu_time, 6.789);
|
|
||||||
EXPECT_FLOAT_EQ(value.knetwork_recv_bytes, 12.3);
|
|
||||||
EXPECT_FLOAT_EQ(value.knetwork_recv_packets, 45.6);
|
|
||||||
EXPECT_FLOAT_EQ(value.knetwork_sent_bytes, 67.8);
|
|
||||||
EXPECT_FLOAT_EQ(value.knetwork_sent_packets, 89);
|
|
||||||
EXPECT_FLOAT_EQ(value.kqos_background, 8.9);
|
|
||||||
EXPECT_FLOAT_EQ(value.kqos_default, 6.78);
|
|
||||||
EXPECT_FLOAT_EQ(value.kqos_legacy, 5.678);
|
|
||||||
EXPECT_FLOAT_EQ(value.kqos_user_initiated, 9.012);
|
|
||||||
EXPECT_FLOAT_EQ(value.kqos_user_interactive, 3.456);
|
|
||||||
EXPECT_FLOAT_EQ(value.kqos_utility, 1.234);
|
|
||||||
EXPECT_FLOAT_EQ(value.kcpu_wakeups, 3.45);
|
|
||||||
}
|
|
||||||
|
|
||||||
coalition_resource_usage MakeResourceUsageWithQOS(int qos_level,
|
|
||||||
base::TimeDelta cpu_time) {
|
|
||||||
coalition_resource_usage result{};
|
|
||||||
result.cpu_time_eqos_len = COALITION_NUM_THREAD_QOS_TYPES;
|
|
||||||
result.cpu_time_eqos[qos_level] = cpu_time.InNanoseconds();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(ResourceCoalitionTests, ComputeEnergyImpactForCoalitionUsage_Individual) {
|
|
||||||
TestResourceCoalition coalition;
|
|
||||||
EXPECT_EQ(0.0, coalition.ComputeEnergyImpactForCoalitionUsage(
|
|
||||||
EnergyImpactCoefficients(), coalition_resource_usage()));
|
|
||||||
|
|
||||||
// Test the coefficients and sample factors individually.
|
|
||||||
EXPECT_DOUBLE_EQ(
|
|
||||||
2.66, coalition.ComputeEnergyImpactForCoalitionUsage(
|
|
||||||
EnergyImpactCoefficients{
|
|
||||||
.kcpu_wakeups = base::Microseconds(200).InSecondsF()},
|
|
||||||
coalition_resource_usage{.platform_idle_wakeups = 133}));
|
|
||||||
|
|
||||||
// Test 100 ms of CPU, which should come out to 8% of a CPU second with a
|
|
||||||
// background QOS discount of rate of 0.8.
|
|
||||||
EXPECT_DOUBLE_EQ(8.0, coalition.ComputeEnergyImpactForCoalitionUsage(
|
|
||||||
EnergyImpactCoefficients{.kqos_background = 0.8},
|
|
||||||
MakeResourceUsageWithQOS(THREAD_QOS_BACKGROUND,
|
|
||||||
base::Milliseconds(100))));
|
|
||||||
EXPECT_DOUBLE_EQ(5.0, coalition.ComputeEnergyImpactForCoalitionUsage(
|
|
||||||
EnergyImpactCoefficients{.kqos_default = 1.0},
|
|
||||||
MakeResourceUsageWithQOS(THREAD_QOS_DEFAULT,
|
|
||||||
base::Milliseconds(50))));
|
|
||||||
EXPECT_DOUBLE_EQ(10.0, coalition.ComputeEnergyImpactForCoalitionUsage(
|
|
||||||
EnergyImpactCoefficients{.kqos_utility = 1.0},
|
|
||||||
MakeResourceUsageWithQOS(
|
|
||||||
THREAD_QOS_UTILITY, base::Milliseconds(100))));
|
|
||||||
EXPECT_DOUBLE_EQ(1.0, coalition.ComputeEnergyImpactForCoalitionUsage(
|
|
||||||
EnergyImpactCoefficients{.kqos_legacy = 1.0},
|
|
||||||
MakeResourceUsageWithQOS(THREAD_QOS_LEGACY,
|
|
||||||
base::Milliseconds(10))));
|
|
||||||
EXPECT_DOUBLE_EQ(1.0,
|
|
||||||
coalition.ComputeEnergyImpactForCoalitionUsage(
|
|
||||||
EnergyImpactCoefficients{.kqos_user_initiated = 1.0},
|
|
||||||
MakeResourceUsageWithQOS(THREAD_QOS_USER_INITIATED,
|
|
||||||
base::Milliseconds(10))));
|
|
||||||
EXPECT_DOUBLE_EQ(1.0,
|
|
||||||
coalition.ComputeEnergyImpactForCoalitionUsage(
|
|
||||||
EnergyImpactCoefficients{.kqos_user_interactive = 1.0},
|
|
||||||
MakeResourceUsageWithQOS(THREAD_QOS_USER_INTERACTIVE,
|
|
||||||
base::Milliseconds(10))));
|
|
||||||
|
|
||||||
EXPECT_DOUBLE_EQ(1.0,
|
|
||||||
coalition.ComputeEnergyImpactForCoalitionUsage(
|
|
||||||
EnergyImpactCoefficients{.kgpu_time = 2.5},
|
|
||||||
coalition_resource_usage{
|
|
||||||
.gpu_time = base::Milliseconds(4).InNanoseconds()}));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(ResourceCoalitionTests, ComputeEnergyImpactForCoalitionUsage_Combined) {
|
|
||||||
// test that the coefficients and samples add up as expected.
|
|
||||||
EnergyImpactCoefficients coefficients{
|
|
||||||
.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 sample{
|
|
||||||
.platform_idle_wakeups = 133,
|
|
||||||
.gpu_time =
|
|
||||||
NsScaleToTimebase(kM1Timebase, base::Milliseconds(4).InNanoseconds()),
|
|
||||||
.cpu_time_eqos_len = COALITION_NUM_THREAD_QOS_TYPES,
|
|
||||||
};
|
|
||||||
sample.cpu_time_eqos[THREAD_QOS_BACKGROUND] =
|
|
||||||
NsScaleToTimebase(kM1Timebase, base::Milliseconds(100).InNanoseconds());
|
|
||||||
sample.cpu_time_eqos[THREAD_QOS_DEFAULT] =
|
|
||||||
NsScaleToTimebase(kM1Timebase, base::Milliseconds(50).InNanoseconds());
|
|
||||||
sample.cpu_time_eqos[THREAD_QOS_UTILITY] =
|
|
||||||
NsScaleToTimebase(kM1Timebase, base::Milliseconds(100).InNanoseconds());
|
|
||||||
sample.cpu_time_eqos[THREAD_QOS_LEGACY] =
|
|
||||||
NsScaleToTimebase(kM1Timebase, base::Milliseconds(10).InNanoseconds());
|
|
||||||
sample.cpu_time_eqos[THREAD_QOS_USER_INITIATED] =
|
|
||||||
NsScaleToTimebase(kM1Timebase, base::Milliseconds(10).InNanoseconds());
|
|
||||||
sample.cpu_time_eqos[THREAD_QOS_USER_INTERACTIVE] =
|
|
||||||
NsScaleToTimebase(kM1Timebase, base::Milliseconds(10).InNanoseconds());
|
|
||||||
|
|
||||||
TestResourceCoalition coalition;
|
|
||||||
coalition.SetMachTimebaseForTesting(kM1Timebase);
|
|
||||||
EXPECT_DOUBLE_EQ(29.66, coalition.ComputeEnergyImpactForCoalitionUsage(
|
|
||||||
coefficients, sample));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(ResourceCoalitionTests, ComputeEnergyImpactForCoalitionUsage_Unused) {
|
|
||||||
// Test that the unused coefficients and sample factors contribute nothing.
|
|
||||||
EnergyImpactCoefficients coefficients{
|
|
||||||
.kdiskio_bytesread = 1000,
|
|
||||||
.kdiskio_byteswritten = 1000,
|
|
||||||
.knetwork_recv_bytes = 1000,
|
|
||||||
.knetwork_recv_packets = 1000,
|
|
||||||
.knetwork_sent_bytes = 1000,
|
|
||||||
.knetwork_sent_packets = 1000,
|
|
||||||
};
|
|
||||||
coalition_resource_usage sample{
|
|
||||||
.tasks_started = 1000,
|
|
||||||
.tasks_exited = 1000,
|
|
||||||
.time_nonempty = 1000,
|
|
||||||
.cpu_time = 1000,
|
|
||||||
.interrupt_wakeups = 1000,
|
|
||||||
.bytesread = 1000,
|
|
||||||
.byteswritten = 1000,
|
|
||||||
.cpu_time_billed_to_me = 1000,
|
|
||||||
.cpu_time_billed_to_others = 1000,
|
|
||||||
.energy = 1000,
|
|
||||||
.logical_immediate_writes = 1000,
|
|
||||||
.logical_deferred_writes = 1000,
|
|
||||||
.logical_invalidated_writes = 1000,
|
|
||||||
.logical_metadata_writes = 1000,
|
|
||||||
.logical_immediate_writes_to_external = 1000,
|
|
||||||
.logical_deferred_writes_to_external = 1000,
|
|
||||||
.logical_invalidated_writes_to_external = 1000,
|
|
||||||
.logical_metadata_writes_to_external = 1000,
|
|
||||||
.energy_billed_to_me = 1000,
|
|
||||||
.energy_billed_to_others = 1000,
|
|
||||||
.cpu_ptime = 1000,
|
|
||||||
.cpu_instructions = 1000,
|
|
||||||
.cpu_cycles = 1000,
|
|
||||||
.fs_metadata_writes = 1000,
|
|
||||||
.pm_writes = 1000,
|
|
||||||
};
|
|
||||||
|
|
||||||
TestResourceCoalition coalition;
|
|
||||||
EXPECT_EQ(
|
|
||||||
0, coalition.ComputeEnergyImpactForCoalitionUsage(coefficients, sample));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(ResourceCoalitionTests, ReadEnergyImpactOrDefaultForBoardId_Exists) {
|
|
||||||
// This board-id should exist.
|
|
||||||
auto coefficients =
|
|
||||||
TestResourceCoalition::ReadEnergyImpactOrDefaultForBoardId(
|
|
||||||
GetTestDataPath(), "Mac-7BA5B2DFE22DDD8C");
|
|
||||||
ASSERT_TRUE(coefficients.has_value());
|
|
||||||
|
|
||||||
// Validate that the default coefficients haven't been loaded.
|
|
||||||
EXPECT_FLOAT_EQ(3.4, coefficients.value().kgpu_time);
|
|
||||||
EXPECT_FLOAT_EQ(0.39, coefficients.value().kqos_background);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(ResourceCoalitionTests, ReadEnergyImpactOrDefaultForBoardId_Default) {
|
|
||||||
// This board-id should not exist.
|
|
||||||
auto coefficients =
|
|
||||||
TestResourceCoalition::ReadEnergyImpactOrDefaultForBoardId(
|
|
||||||
GetTestDataPath(), "Mac-031B6874CF7F642A");
|
|
||||||
ASSERT_TRUE(coefficients.has_value());
|
|
||||||
|
|
||||||
// Validate that the default coefficients were loaded.
|
|
||||||
EXPECT_FLOAT_EQ(0, coefficients.value().kgpu_time);
|
|
||||||
EXPECT_FLOAT_EQ(0.8, coefficients.value().kqos_background);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(ResourceCoalitionTests, ReadEnergyImpactOrDefaultForBoardId_NoFiles) {
|
|
||||||
// This directory shouldn't exist, so nothing should be loaded.
|
|
||||||
EXPECT_FALSE(
|
|
||||||
TestResourceCoalition::ReadEnergyImpactOrDefaultForBoardId(
|
|
||||||
GetTestDataPath().Append("nonexistent"), "Mac-7BA5B2DFE22DDD8C")
|
|
||||||
.has_value());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(ResourceCoalitionTests, MaybeGetBoardIdForThisMachine) {
|
|
||||||
// This can't really be tested except that the contract holds one way
|
|
||||||
// or the other.
|
|
||||||
auto board_id = TestResourceCoalition::MaybeGetBoardIdForThisMachine();
|
|
||||||
if (board_id.has_value()) {
|
|
||||||
EXPECT_FALSE(board_id.value().empty());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
} // namespace performance_monitor
|
} // namespace performance_monitor
|
||||||
|
@ -491,7 +491,10 @@ test("components_unittests") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (is_mac) {
|
if (is_mac) {
|
||||||
deps += [ "//components/metal_util:unit_tests" ]
|
deps += [
|
||||||
|
"//components/metal_util:unit_tests",
|
||||||
|
"//components/power_metrics:unit_tests",
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toolkit_views) {
|
if (toolkit_views) {
|
||||||
|
@ -5,6 +5,10 @@
|
|||||||
if (is_mac) {
|
if (is_mac) {
|
||||||
static_library("power_metrics") {
|
static_library("power_metrics") {
|
||||||
sources = [
|
sources = [
|
||||||
|
"energy_impact_mac.h",
|
||||||
|
"energy_impact_mac.mm",
|
||||||
|
"mach_time_mac.h",
|
||||||
|
"mach_time_mac.mm",
|
||||||
"resource_coalition_internal_types_mac.h",
|
"resource_coalition_internal_types_mac.h",
|
||||||
"resource_coalition_mac.h",
|
"resource_coalition_mac.h",
|
||||||
"resource_coalition_mac.mm",
|
"resource_coalition_mac.mm",
|
||||||
@ -12,4 +16,18 @@ if (is_mac) {
|
|||||||
|
|
||||||
deps = [ "//base" ]
|
deps = [ "//base" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
source_set("unit_tests") {
|
||||||
|
testonly = true
|
||||||
|
|
||||||
|
sources = [ "energy_impact_mac_unittest.mm" ]
|
||||||
|
|
||||||
|
data = [ "test/data/" ]
|
||||||
|
|
||||||
|
deps = [
|
||||||
|
":power_metrics",
|
||||||
|
"//base",
|
||||||
|
"//testing/gtest",
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
95
components/power_metrics/energy_impact_mac.h
Normal file
95
components/power_metrics/energy_impact_mac.h
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
#ifndef COMPONENTS_POWER_METRICS_ENERGY_IMPACT_MAC_H_
|
||||||
|
#define COMPONENTS_POWER_METRICS_ENERGY_IMPACT_MAC_H_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "base/files/file_path.h"
|
||||||
|
#include "base/time/time.h"
|
||||||
|
#include "third_party/abseil-cpp/absl/types/optional.h"
|
||||||
|
|
||||||
|
struct coalition_resource_usage;
|
||||||
|
|
||||||
|
namespace power_metrics {
|
||||||
|
|
||||||
|
// Coefficients used to compute the Energy Impact score from resource
|
||||||
|
// coalition metrics. The order of members mimics the order of keys in the
|
||||||
|
// plist files in /usr/share/pmenergy.
|
||||||
|
struct EnergyImpactCoefficients {
|
||||||
|
double kcpu_time;
|
||||||
|
|
||||||
|
// In units of seconds/event.
|
||||||
|
double kcpu_wakeups;
|
||||||
|
|
||||||
|
// Coefficients for CPU usage at different QOS.
|
||||||
|
// Strangely there's no coefficient for maintenance QOS.
|
||||||
|
double kqos_default;
|
||||||
|
double kqos_background;
|
||||||
|
double kqos_utility;
|
||||||
|
double kqos_legacy;
|
||||||
|
double kqos_user_initiated;
|
||||||
|
double kqos_user_interactive;
|
||||||
|
|
||||||
|
double kdiskio_bytesread;
|
||||||
|
double kdiskio_byteswritten;
|
||||||
|
|
||||||
|
double kgpu_time;
|
||||||
|
|
||||||
|
double knetwork_recv_bytes;
|
||||||
|
double knetwork_recv_packets;
|
||||||
|
double knetwork_sent_bytes;
|
||||||
|
double knetwork_sent_packets;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reads the Energy Impact coefficients for the current machine from disk, or
|
||||||
|
// default coefficients if coefficients are not available for the current
|
||||||
|
// machine.
|
||||||
|
absl::optional<EnergyImpactCoefficients>
|
||||||
|
ReadCoefficientsForCurrentMachineOrDefault();
|
||||||
|
|
||||||
|
// Computes the Energy Impact score for the resource consumption data in
|
||||||
|
// |coalition_resource_usage| using |coefficients|.
|
||||||
|
//
|
||||||
|
// The Energy Impact (EI) score is referenced to CPU time, such that 10ms CPU
|
||||||
|
// time appears to be equivalent to 1 EI. The Activity Monitor presents EI
|
||||||
|
// rates to the user in units of 10ms/s of CPU time. This means a process that
|
||||||
|
// consumes 1000ms/s or 100% CPU, at default QOS, is rated 100 EI, making the
|
||||||
|
// two units somewhat relatable. Note that this only has relevance on Intel
|
||||||
|
// architecture, as it looks like on M1 architecture macOS implements more
|
||||||
|
// granular, and hopefully more accurate, energy metering on the fly.
|
||||||
|
double ComputeEnergyImpactForResourceUsage(
|
||||||
|
const coalition_resource_usage& coalition_resource_usage,
|
||||||
|
const EnergyImpactCoefficients& coefficients,
|
||||||
|
const mach_timebase_info_data_t& mach_timebase);
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
// Reads the coefficients from the "energy_constants" sub-dictionary of the
|
||||||
|
// plist file at |plist_file|. This is exposed for testing, production code
|
||||||
|
// should use ReadCoefficientsForCurrentMachineOrDefault().
|
||||||
|
absl::optional<EnergyImpactCoefficients> ReadCoefficientsFromPath(
|
||||||
|
const base::FilePath& plist_file);
|
||||||
|
|
||||||
|
// Given a |directory| and a |board_id|, read the plist file for the board id
|
||||||
|
// from the directory, or if not available, read the default file.
|
||||||
|
// Returns true if either file can be loaded, false otherwise. This is exposed
|
||||||
|
// for testing, production code should use
|
||||||
|
// ReadCoefficientsForCurrentMachineOrDefault().
|
||||||
|
absl::optional<EnergyImpactCoefficients> ReadCoefficientsForBoardIdOrDefault(
|
||||||
|
const base::FilePath& directory,
|
||||||
|
const std::string& board_id);
|
||||||
|
|
||||||
|
// Returns the board id to use for reading the Energy Impact coefficients for
|
||||||
|
// the current machine. This appears to work for Intel Macs only. This is
|
||||||
|
// exposed for testing, production code should use
|
||||||
|
// ReadCoefficientsForCurrentMachineOrDefault().
|
||||||
|
absl::optional<std::string> GetBoardIdForThisMachine();
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
|
||||||
|
} // namespace power_metrics
|
||||||
|
|
||||||
|
#endif // COMPONENTS_POWER_METRICS_ENERGY_IMPACT_MAC_H_
|
195
components/power_metrics/energy_impact_mac.mm
Normal file
195
components/power_metrics/energy_impact_mac.mm
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
// 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/energy_impact_mac.h"
|
||||||
|
|
||||||
|
#include <Foundation/Foundation.h>
|
||||||
|
#import <IOKit/IOKitLib.h>
|
||||||
|
|
||||||
|
#include "base/mac/foundation_util.h"
|
||||||
|
#include "base/mac/scoped_cftyperef.h"
|
||||||
|
#include "base/mac/scoped_ioobject.h"
|
||||||
|
#include "base/strings/sys_string_conversions.h"
|
||||||
|
#include "base/time/time.h"
|
||||||
|
#include "components/power_metrics/mach_time_mac.h"
|
||||||
|
#include "components/power_metrics/resource_coalition_mac.h"
|
||||||
|
|
||||||
|
namespace power_metrics {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
NSDictionary* MaybeGetDictionaryFromPath(const base::FilePath& path) {
|
||||||
|
// The folder where the energy coefficient plist files are stored.
|
||||||
|
NSString* plist_path_string = base::SysUTF8ToNSString(path.value().c_str());
|
||||||
|
return [NSDictionary dictionaryWithContentsOfFile:plist_path_string];
|
||||||
|
}
|
||||||
|
|
||||||
|
double GetNamedCoefficientOrZero(NSDictionary* dict, NSString* key) {
|
||||||
|
NSObject* value = [dict objectForKey:key];
|
||||||
|
NSNumber* num = base::mac::ObjCCast<NSNumber>(value);
|
||||||
|
return [num floatValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
absl::optional<EnergyImpactCoefficients>
|
||||||
|
ReadCoefficientsForCurrentMachineOrDefault() {
|
||||||
|
absl::optional<std::string> board_id = internal::GetBoardIdForThisMachine();
|
||||||
|
if (!board_id.has_value())
|
||||||
|
return absl::nullopt;
|
||||||
|
|
||||||
|
return internal::ReadCoefficientsForBoardIdOrDefault(
|
||||||
|
base::FilePath(FILE_PATH_LITERAL("/usr/share/pmenergy")),
|
||||||
|
board_id.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
double ComputeEnergyImpactForResourceUsage(
|
||||||
|
const coalition_resource_usage& data_sample,
|
||||||
|
const EnergyImpactCoefficients& coefficients,
|
||||||
|
const mach_timebase_info_data_t& mach_timebase) {
|
||||||
|
// TODO(https://crbug.com/1249536): The below coefficients are not used
|
||||||
|
// for now. Their units are unknown, and in the case of the network-related
|
||||||
|
// coefficients, it's not clear how to sample the data.
|
||||||
|
//
|
||||||
|
// coefficients.kdiskio_bytesread;
|
||||||
|
// coefficients.kdiskio_byteswritten;
|
||||||
|
// coefficients.knetwork_recv_bytes;
|
||||||
|
// coefficients.knetwork_recv_packets;
|
||||||
|
// coefficients.knetwork_sent_bytes;
|
||||||
|
// coefficients.knetwork_sent_packets;
|
||||||
|
|
||||||
|
// The cumulative CPU usage in |data_sample| is in units of ns, and
|
||||||
|
// |cpu_time_equivalent_ns| is computed in CPU ns up to the end of this
|
||||||
|
// function, where it's converted to units of EnergyImpact.
|
||||||
|
double cpu_time_equivalent_ns = 0.0;
|
||||||
|
|
||||||
|
// The kcpu_wakeups coefficient on disk is in seconds, but our
|
||||||
|
// intermediate result is in ns, so convert to ns on the fly.
|
||||||
|
cpu_time_equivalent_ns += coefficients.kcpu_wakeups *
|
||||||
|
base::Time::kNanosecondsPerSecond *
|
||||||
|
data_sample.platform_idle_wakeups;
|
||||||
|
|
||||||
|
// Presumably the kgpu_time coefficient has suitable units for the
|
||||||
|
// conversion of GPU time energy to CPU time energy. There is a fairly
|
||||||
|
// wide spread on this constant seen in /usr/share/pmenergy. On
|
||||||
|
// macOS 11.5.2 the spread is from 0 through 5.9.
|
||||||
|
cpu_time_equivalent_ns += coefficients.kgpu_time *
|
||||||
|
MachTimeToNs(data_sample.gpu_time, mach_timebase);
|
||||||
|
|
||||||
|
cpu_time_equivalent_ns +=
|
||||||
|
coefficients.kqos_background *
|
||||||
|
MachTimeToNs(data_sample.cpu_time_eqos[THREAD_QOS_BACKGROUND],
|
||||||
|
mach_timebase);
|
||||||
|
cpu_time_equivalent_ns +=
|
||||||
|
coefficients.kqos_default *
|
||||||
|
MachTimeToNs(data_sample.cpu_time_eqos[THREAD_QOS_DEFAULT],
|
||||||
|
mach_timebase);
|
||||||
|
cpu_time_equivalent_ns +=
|
||||||
|
coefficients.kqos_legacy *
|
||||||
|
MachTimeToNs(data_sample.cpu_time_eqos[THREAD_QOS_LEGACY], mach_timebase);
|
||||||
|
cpu_time_equivalent_ns +=
|
||||||
|
coefficients.kqos_user_initiated *
|
||||||
|
MachTimeToNs(data_sample.cpu_time_eqos[THREAD_QOS_USER_INITIATED],
|
||||||
|
mach_timebase);
|
||||||
|
cpu_time_equivalent_ns +=
|
||||||
|
coefficients.kqos_user_interactive *
|
||||||
|
MachTimeToNs(data_sample.cpu_time_eqos[THREAD_QOS_USER_INTERACTIVE],
|
||||||
|
mach_timebase);
|
||||||
|
cpu_time_equivalent_ns +=
|
||||||
|
coefficients.kqos_utility *
|
||||||
|
MachTimeToNs(data_sample.cpu_time_eqos[THREAD_QOS_UTILITY],
|
||||||
|
mach_timebase);
|
||||||
|
|
||||||
|
// The conversion ratio for CPU time/EnergyImpact is ns/10ms
|
||||||
|
constexpr double kNsToEI = 1E-7;
|
||||||
|
return cpu_time_equivalent_ns * kNsToEI;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
absl::optional<EnergyImpactCoefficients> ReadCoefficientsFromPath(
|
||||||
|
const base::FilePath& plist_file) {
|
||||||
|
@autoreleasepool {
|
||||||
|
NSDictionary* dict = MaybeGetDictionaryFromPath(plist_file);
|
||||||
|
if (!dict)
|
||||||
|
return absl::nullopt;
|
||||||
|
|
||||||
|
NSDictionary* energy_constants = [dict objectForKey:@"energy_constants"];
|
||||||
|
if (!energy_constants)
|
||||||
|
return absl::nullopt;
|
||||||
|
|
||||||
|
EnergyImpactCoefficients coefficients{};
|
||||||
|
coefficients.kcpu_time =
|
||||||
|
GetNamedCoefficientOrZero(energy_constants, @"kcpu_time");
|
||||||
|
coefficients.kcpu_wakeups =
|
||||||
|
GetNamedCoefficientOrZero(energy_constants, @"kcpu_wakeups");
|
||||||
|
|
||||||
|
coefficients.kqos_default =
|
||||||
|
GetNamedCoefficientOrZero(energy_constants, @"kqos_default");
|
||||||
|
coefficients.kqos_background =
|
||||||
|
GetNamedCoefficientOrZero(energy_constants, @"kqos_background");
|
||||||
|
coefficients.kqos_utility =
|
||||||
|
GetNamedCoefficientOrZero(energy_constants, @"kqos_utility");
|
||||||
|
coefficients.kqos_legacy =
|
||||||
|
GetNamedCoefficientOrZero(energy_constants, @"kqos_legacy");
|
||||||
|
coefficients.kqos_user_initiated =
|
||||||
|
GetNamedCoefficientOrZero(energy_constants, @"kqos_user_initiated");
|
||||||
|
coefficients.kqos_user_interactive =
|
||||||
|
GetNamedCoefficientOrZero(energy_constants, @"kqos_user_interactive");
|
||||||
|
|
||||||
|
coefficients.kdiskio_bytesread =
|
||||||
|
GetNamedCoefficientOrZero(energy_constants, @"kdiskio_bytesread");
|
||||||
|
coefficients.kdiskio_byteswritten =
|
||||||
|
GetNamedCoefficientOrZero(energy_constants, @"kdiskio_byteswritten");
|
||||||
|
|
||||||
|
coefficients.kgpu_time =
|
||||||
|
GetNamedCoefficientOrZero(energy_constants, @"kgpu_time");
|
||||||
|
|
||||||
|
coefficients.knetwork_recv_bytes =
|
||||||
|
GetNamedCoefficientOrZero(energy_constants, @"knetwork_recv_bytes");
|
||||||
|
coefficients.knetwork_recv_packets =
|
||||||
|
GetNamedCoefficientOrZero(energy_constants, @"knetwork_recv_packets");
|
||||||
|
coefficients.knetwork_sent_bytes =
|
||||||
|
GetNamedCoefficientOrZero(energy_constants, @"knetwork_sent_bytes");
|
||||||
|
coefficients.knetwork_sent_packets =
|
||||||
|
GetNamedCoefficientOrZero(energy_constants, @"knetwork_sent_packets");
|
||||||
|
|
||||||
|
return coefficients;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::optional<EnergyImpactCoefficients> ReadCoefficientsForBoardIdOrDefault(
|
||||||
|
const base::FilePath& directory,
|
||||||
|
const std::string& board_id) {
|
||||||
|
auto coefficients = ReadCoefficientsFromPath(
|
||||||
|
directory.Append(board_id).AddExtension(FILE_PATH_LITERAL("plist")));
|
||||||
|
if (coefficients.has_value())
|
||||||
|
return coefficients;
|
||||||
|
|
||||||
|
return ReadCoefficientsFromPath(
|
||||||
|
directory.Append(FILE_PATH_LITERAL("default.plist")));
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::optional<std::string> GetBoardIdForThisMachine() {
|
||||||
|
base::mac::ScopedIOObject<io_service_t> platform_expert(
|
||||||
|
IOServiceGetMatchingService(kIOMasterPortDefault,
|
||||||
|
IOServiceMatching("IOPlatformExpertDevice")));
|
||||||
|
if (!platform_expert)
|
||||||
|
return absl::nullopt;
|
||||||
|
|
||||||
|
// This is what libpmenergy is observed to do in order to retrieve the correct
|
||||||
|
// coefficients file for the local computer.
|
||||||
|
base::ScopedCFTypeRef<CFDataRef> board_id_data(
|
||||||
|
base::mac::CFCast<CFDataRef>(IORegistryEntryCreateCFProperty(
|
||||||
|
platform_expert, CFSTR("board-id"), kCFAllocatorDefault, 0)));
|
||||||
|
|
||||||
|
if (!board_id_data)
|
||||||
|
return absl::nullopt;
|
||||||
|
|
||||||
|
return reinterpret_cast<const char*>(CFDataGetBytePtr(board_id_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
|
||||||
|
} // namespace power_metrics
|
251
components/power_metrics/energy_impact_mac_unittest.mm
Normal file
251
components/power_metrics/energy_impact_mac_unittest.mm
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
// 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/energy_impact_mac.h"
|
||||||
|
|
||||||
|
#include "base/base_paths.h"
|
||||||
|
#include "base/path_service.h"
|
||||||
|
#include "components/power_metrics/resource_coalition_mac.h"
|
||||||
|
#include "testing/gtest/include/gtest/gtest.h"
|
||||||
|
|
||||||
|
namespace power_metrics {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr mach_timebase_info_data_t kIntelTimebase = {1, 1};
|
||||||
|
constexpr mach_timebase_info_data_t kM1Timebase = {125, 3};
|
||||||
|
|
||||||
|
base::FilePath GetTestDataPath() {
|
||||||
|
base::FilePath test_path;
|
||||||
|
EXPECT_TRUE(base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &test_path));
|
||||||
|
test_path = test_path.Append(FILE_PATH_LITERAL("components"));
|
||||||
|
test_path = test_path.Append(FILE_PATH_LITERAL("power_metrics"));
|
||||||
|
test_path = test_path.Append(FILE_PATH_LITERAL("test"));
|
||||||
|
test_path = test_path.Append(FILE_PATH_LITERAL("data"));
|
||||||
|
return test_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
coalition_resource_usage MakeResourceUsageWithQOS(int qos_level,
|
||||||
|
base::TimeDelta cpu_time) {
|
||||||
|
coalition_resource_usage result{};
|
||||||
|
result.cpu_time_eqos_len = COALITION_NUM_THREAD_QOS_TYPES;
|
||||||
|
result.cpu_time_eqos[qos_level] = cpu_time.InNanoseconds();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(EnergyImpactTest, ReadCoefficientsFromPath) {
|
||||||
|
base::FilePath test_path = GetTestDataPath();
|
||||||
|
|
||||||
|
// Validate that attempting to read from a non-exisent file fails.
|
||||||
|
auto coefficients = internal::ReadCoefficientsFromPath(
|
||||||
|
test_path.Append(FILE_PATH_LITERAL("does-not-exist.plist")));
|
||||||
|
EXPECT_FALSE(coefficients.has_value());
|
||||||
|
|
||||||
|
// Validate that a well-formed file returns the expected coefficients.
|
||||||
|
coefficients = internal::ReadCoefficientsFromPath(
|
||||||
|
test_path.Append(FILE_PATH_LITERAL("test.plist")));
|
||||||
|
ASSERT_TRUE(coefficients.has_value());
|
||||||
|
|
||||||
|
const EnergyImpactCoefficients& value = coefficients.value();
|
||||||
|
EXPECT_FLOAT_EQ(value.kcpu_time, 1.23);
|
||||||
|
EXPECT_FLOAT_EQ(value.kdiskio_bytesread, 7.89);
|
||||||
|
EXPECT_FLOAT_EQ(value.kdiskio_byteswritten, 1.2345);
|
||||||
|
EXPECT_FLOAT_EQ(value.kgpu_time, 6.789);
|
||||||
|
EXPECT_FLOAT_EQ(value.knetwork_recv_bytes, 12.3);
|
||||||
|
EXPECT_FLOAT_EQ(value.knetwork_recv_packets, 45.6);
|
||||||
|
EXPECT_FLOAT_EQ(value.knetwork_sent_bytes, 67.8);
|
||||||
|
EXPECT_FLOAT_EQ(value.knetwork_sent_packets, 89);
|
||||||
|
EXPECT_FLOAT_EQ(value.kqos_background, 8.9);
|
||||||
|
EXPECT_FLOAT_EQ(value.kqos_default, 6.78);
|
||||||
|
EXPECT_FLOAT_EQ(value.kqos_legacy, 5.678);
|
||||||
|
EXPECT_FLOAT_EQ(value.kqos_user_initiated, 9.012);
|
||||||
|
EXPECT_FLOAT_EQ(value.kqos_user_interactive, 3.456);
|
||||||
|
EXPECT_FLOAT_EQ(value.kqos_utility, 1.234);
|
||||||
|
EXPECT_FLOAT_EQ(value.kcpu_wakeups, 3.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EnergyImpactTest, ReadCoefficientsForBoardIdOrDefault_Exists) {
|
||||||
|
// This board-id should exist.
|
||||||
|
auto coefficients = internal::ReadCoefficientsForBoardIdOrDefault(
|
||||||
|
GetTestDataPath(), "Mac-7BA5B2DFE22DDD8C");
|
||||||
|
ASSERT_TRUE(coefficients.has_value());
|
||||||
|
|
||||||
|
// Validate that the default coefficients haven't been loaded.
|
||||||
|
EXPECT_FLOAT_EQ(3.4, coefficients.value().kgpu_time);
|
||||||
|
EXPECT_FLOAT_EQ(0.39, coefficients.value().kqos_background);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EnergyImpactTest, ReadCoefficientsForBoardIdOrDefault_Default) {
|
||||||
|
// This board-id should not exist.
|
||||||
|
auto coefficients = internal::ReadCoefficientsForBoardIdOrDefault(
|
||||||
|
GetTestDataPath(), "Mac-031B6874CF7F642A");
|
||||||
|
ASSERT_TRUE(coefficients.has_value());
|
||||||
|
|
||||||
|
// Validate that the default coefficients were loaded.
|
||||||
|
EXPECT_FLOAT_EQ(0, coefficients.value().kgpu_time);
|
||||||
|
EXPECT_FLOAT_EQ(0.8, coefficients.value().kqos_background);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EnergyImpactTest,
|
||||||
|
ReadCoefficientsForBoardIdOrDefault_NonExistentDirectory) {
|
||||||
|
// This directory shouldn't exist, so nothing should be loaded.
|
||||||
|
EXPECT_FALSE(
|
||||||
|
internal::ReadCoefficientsForBoardIdOrDefault(
|
||||||
|
GetTestDataPath().Append("nonexistent"), "Mac-7BA5B2DFE22DDD8C")
|
||||||
|
.has_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(EnergyImpactTest, GetBoardIdForThisMachine) {
|
||||||
|
// This can't really be tested except that the contract holds one way
|
||||||
|
// or the other.
|
||||||
|
auto board_id = internal::GetBoardIdForThisMachine();
|
||||||
|
if (board_id.has_value()) {
|
||||||
|
EXPECT_FALSE(board_id.value().empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the Energy Impact score when there is a single source of energy
|
||||||
|
// consumption (only one member set in `coalition_resource_usage`).
|
||||||
|
TEST(EnergyImpactTest, ComputeEnergyImpactForResourceUsage_Individual) {
|
||||||
|
EXPECT_EQ(0.0, ComputeEnergyImpactForResourceUsage(coalition_resource_usage(),
|
||||||
|
EnergyImpactCoefficients{},
|
||||||
|
kIntelTimebase));
|
||||||
|
|
||||||
|
// Test the coefficients and sample factors individually.
|
||||||
|
EXPECT_DOUBLE_EQ(
|
||||||
|
2.66, ComputeEnergyImpactForResourceUsage(
|
||||||
|
coalition_resource_usage{.platform_idle_wakeups = 133},
|
||||||
|
EnergyImpactCoefficients{
|
||||||
|
.kcpu_wakeups = base::Microseconds(200).InSecondsF()},
|
||||||
|
kIntelTimebase));
|
||||||
|
|
||||||
|
// Test 100 ms of CPU, which should come out to 8% of a CPU second with a
|
||||||
|
// background QOS discount of rate of 0.8.
|
||||||
|
EXPECT_DOUBLE_EQ(8.0, ComputeEnergyImpactForResourceUsage(
|
||||||
|
MakeResourceUsageWithQOS(THREAD_QOS_BACKGROUND,
|
||||||
|
base::Milliseconds(100)),
|
||||||
|
EnergyImpactCoefficients{.kqos_background = 0.8},
|
||||||
|
kIntelTimebase));
|
||||||
|
EXPECT_DOUBLE_EQ(
|
||||||
|
5.0,
|
||||||
|
ComputeEnergyImpactForResourceUsage(
|
||||||
|
MakeResourceUsageWithQOS(THREAD_QOS_DEFAULT, base::Milliseconds(50)),
|
||||||
|
EnergyImpactCoefficients{.kqos_default = 1.0}, kIntelTimebase));
|
||||||
|
EXPECT_DOUBLE_EQ(
|
||||||
|
10.0,
|
||||||
|
ComputeEnergyImpactForResourceUsage(
|
||||||
|
MakeResourceUsageWithQOS(THREAD_QOS_UTILITY, base::Milliseconds(100)),
|
||||||
|
EnergyImpactCoefficients{.kqos_utility = 1.0}, kIntelTimebase));
|
||||||
|
EXPECT_DOUBLE_EQ(
|
||||||
|
1.0,
|
||||||
|
ComputeEnergyImpactForResourceUsage(
|
||||||
|
MakeResourceUsageWithQOS(THREAD_QOS_LEGACY, base::Milliseconds(10)),
|
||||||
|
EnergyImpactCoefficients{.kqos_legacy = 1.0}, kIntelTimebase));
|
||||||
|
EXPECT_DOUBLE_EQ(1.0,
|
||||||
|
ComputeEnergyImpactForResourceUsage(
|
||||||
|
MakeResourceUsageWithQOS(THREAD_QOS_USER_INITIATED,
|
||||||
|
base::Milliseconds(10)),
|
||||||
|
EnergyImpactCoefficients{.kqos_user_initiated = 1.0},
|
||||||
|
kIntelTimebase));
|
||||||
|
EXPECT_DOUBLE_EQ(1.0,
|
||||||
|
ComputeEnergyImpactForResourceUsage(
|
||||||
|
MakeResourceUsageWithQOS(THREAD_QOS_USER_INTERACTIVE,
|
||||||
|
base::Milliseconds(10)),
|
||||||
|
EnergyImpactCoefficients{.kqos_user_interactive = 1.0},
|
||||||
|
kIntelTimebase));
|
||||||
|
EXPECT_DOUBLE_EQ(
|
||||||
|
1.0, ComputeEnergyImpactForResourceUsage(
|
||||||
|
coalition_resource_usage{
|
||||||
|
.gpu_time = base::Milliseconds(4).InNanoseconds()},
|
||||||
|
EnergyImpactCoefficients{.kgpu_time = 2.5}, kIntelTimebase));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the Energy Impact score when there are multiple sources of energy
|
||||||
|
// consumption (multiple members set in `coalition_resource_usage`).
|
||||||
|
TEST(EnergyImpactTest, ComputeEnergyImpactForResourceUsage_Combined) {
|
||||||
|
EnergyImpactCoefficients coefficients{
|
||||||
|
.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 sample{
|
||||||
|
.platform_idle_wakeups = 133,
|
||||||
|
.gpu_time =
|
||||||
|
NsScaleToTimebase(kM1Timebase, base::Milliseconds(4).InNanoseconds()),
|
||||||
|
.cpu_time_eqos_len = COALITION_NUM_THREAD_QOS_TYPES,
|
||||||
|
};
|
||||||
|
sample.cpu_time_eqos[THREAD_QOS_BACKGROUND] =
|
||||||
|
NsScaleToTimebase(kM1Timebase, base::Milliseconds(100).InNanoseconds());
|
||||||
|
sample.cpu_time_eqos[THREAD_QOS_DEFAULT] =
|
||||||
|
NsScaleToTimebase(kM1Timebase, base::Milliseconds(50).InNanoseconds());
|
||||||
|
sample.cpu_time_eqos[THREAD_QOS_UTILITY] =
|
||||||
|
NsScaleToTimebase(kM1Timebase, base::Milliseconds(100).InNanoseconds());
|
||||||
|
sample.cpu_time_eqos[THREAD_QOS_LEGACY] =
|
||||||
|
NsScaleToTimebase(kM1Timebase, base::Milliseconds(10).InNanoseconds());
|
||||||
|
sample.cpu_time_eqos[THREAD_QOS_USER_INITIATED] =
|
||||||
|
NsScaleToTimebase(kM1Timebase, base::Milliseconds(10).InNanoseconds());
|
||||||
|
sample.cpu_time_eqos[THREAD_QOS_USER_INTERACTIVE] =
|
||||||
|
NsScaleToTimebase(kM1Timebase, base::Milliseconds(10).InNanoseconds());
|
||||||
|
|
||||||
|
EXPECT_DOUBLE_EQ(29.66, ComputeEnergyImpactForResourceUsage(
|
||||||
|
sample, coefficients, kM1Timebase));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the Energy Impact score when fields of `coalition_resource_usage` that
|
||||||
|
// don't contribute to the score are set.
|
||||||
|
TEST(EnergyImpactTest, ComputeEnergyImpactForResourceUsage_Unused) {
|
||||||
|
EnergyImpactCoefficients coefficients{
|
||||||
|
.kdiskio_bytesread = 1000,
|
||||||
|
.kdiskio_byteswritten = 1000,
|
||||||
|
.knetwork_recv_bytes = 1000,
|
||||||
|
.knetwork_recv_packets = 1000,
|
||||||
|
.knetwork_sent_bytes = 1000,
|
||||||
|
.knetwork_sent_packets = 1000,
|
||||||
|
};
|
||||||
|
coalition_resource_usage sample{
|
||||||
|
.tasks_started = 1000,
|
||||||
|
.tasks_exited = 1000,
|
||||||
|
.time_nonempty = 1000,
|
||||||
|
.cpu_time = 1000,
|
||||||
|
.interrupt_wakeups = 1000,
|
||||||
|
.bytesread = 1000,
|
||||||
|
.byteswritten = 1000,
|
||||||
|
.cpu_time_billed_to_me = 1000,
|
||||||
|
.cpu_time_billed_to_others = 1000,
|
||||||
|
.energy = 1000,
|
||||||
|
.logical_immediate_writes = 1000,
|
||||||
|
.logical_deferred_writes = 1000,
|
||||||
|
.logical_invalidated_writes = 1000,
|
||||||
|
.logical_metadata_writes = 1000,
|
||||||
|
.logical_immediate_writes_to_external = 1000,
|
||||||
|
.logical_deferred_writes_to_external = 1000,
|
||||||
|
.logical_invalidated_writes_to_external = 1000,
|
||||||
|
.logical_metadata_writes_to_external = 1000,
|
||||||
|
.energy_billed_to_me = 1000,
|
||||||
|
.energy_billed_to_others = 1000,
|
||||||
|
.cpu_ptime = 1000,
|
||||||
|
.cpu_instructions = 1000,
|
||||||
|
.cpu_cycles = 1000,
|
||||||
|
.fs_metadata_writes = 1000,
|
||||||
|
.pm_writes = 1000,
|
||||||
|
};
|
||||||
|
|
||||||
|
EXPECT_EQ(0, ComputeEnergyImpactForResourceUsage(sample, coefficients,
|
||||||
|
kM1Timebase));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace power_metrics
|
23
components/power_metrics/mach_time_mac.h
Normal file
23
components/power_metrics/mach_time_mac.h
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
#ifndef COMPONENTS_POWER_METRICS_MACH_TIME_MAC_H_
|
||||||
|
#define COMPONENTS_POWER_METRICS_MACH_TIME_MAC_H_
|
||||||
|
|
||||||
|
#include <mach/mach_time.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
namespace power_metrics {
|
||||||
|
|
||||||
|
// Converts |mach_time| to nanoseconds, using the multiplier in |mach_timebase|.
|
||||||
|
uint64_t MachTimeToNs(uint64_t mach_time,
|
||||||
|
const mach_timebase_info_data_t& mach_timebase);
|
||||||
|
|
||||||
|
// Retrieves the |mach_timebase| to convert |mach_time| obtained on this system
|
||||||
|
// to nanoseconds.
|
||||||
|
mach_timebase_info_data_t GetSystemMachTimeBase();
|
||||||
|
|
||||||
|
} // namespace power_metrics
|
||||||
|
|
||||||
|
#endif // COMPONENTS_POWER_METRICS_MACH_TIME_MAC_H_
|
30
components/power_metrics/mach_time_mac.mm
Normal file
30
components/power_metrics/mach_time_mac.mm
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// 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/mach_time_mac.h"
|
||||||
|
|
||||||
|
#include "base/check.h"
|
||||||
|
#include "base/mac/mach_logging.h"
|
||||||
|
|
||||||
|
namespace power_metrics {
|
||||||
|
|
||||||
|
uint64_t MachTimeToNs(uint64_t mach_time,
|
||||||
|
const mach_timebase_info_data_t& mach_timebase) {
|
||||||
|
if (mach_timebase.numer == mach_timebase.denom)
|
||||||
|
return mach_time;
|
||||||
|
|
||||||
|
CHECK(!__builtin_umulll_overflow(mach_time, mach_timebase.numer, &mach_time));
|
||||||
|
return mach_time / mach_timebase.denom;
|
||||||
|
}
|
||||||
|
|
||||||
|
mach_timebase_info_data_t GetSystemMachTimeBase() {
|
||||||
|
mach_timebase_info_data_t info;
|
||||||
|
kern_return_t kr = mach_timebase_info(&info);
|
||||||
|
MACH_DCHECK(kr == KERN_SUCCESS, kr) << "mach_timebase_info";
|
||||||
|
DCHECK(info.numer);
|
||||||
|
DCHECK(info.denom);
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace power_metrics
|
Reference in New Issue
Block a user