0

[power_sampler] Add SMC sampler.

The SMC sampler produces instant reading of these values on Intel Macs:
- Total power consumption
- CPU power consumption
- Integrated GPU power consumption
- GPU 0 and GPU 1 power consumption

This also fixes issues with the code that records SMC histograms:
- The Power.Mac.GPU1 histogram now records data from GPU1, not GPU0
  (see https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/metrics/power/power_metrics_provider_mac.mm;l=221;drc=a0c577275320741e104ae963aac4d8d7388da800)
- The Impl is actually deleted when metrics recording stops (previously,
  a reference was released
  https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/metrics/power/power_metrics_provider_mac.mm;l=313;drc=a0c577275320741e104ae963aac4d8d7388da800
  but another reference remained in the delayed callback).

Bug: 1254332
Change-Id: I3394892faef2bc934ea414832eef46e9177df6d6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3302853
Commit-Queue: François Doray <fdoray@chromium.org>
Reviewed-by: Etienne Pierre-Doray <etiennep@chromium.org>
Cr-Commit-Position: refs/heads/main@{#946141}
This commit is contained in:
Francois Doray
2021-11-29 21:22:10 +00:00
committed by Chromium LUCI CQ
parent 833a36e2b2
commit 9b7f7b5b80
11 changed files with 615 additions and 213 deletions

@ -7,8 +7,8 @@
#include "components/metrics/metrics_provider.h"
#include "base/bind.h"
#include "chrome/browser/metrics/power/battery_level_provider.h"
#include "base/threading/sequence_bound.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
class PowerMetricsProvider : public metrics::MetricsProvider {
public:
@ -23,8 +23,9 @@ class PowerMetricsProvider : public metrics::MetricsProvider {
void OnRecordingDisabled() override;
private:
// Records metrics from the ThreadPool.
class Impl;
scoped_refptr<Impl> impl_;
absl::optional<base::SequenceBound<Impl>> impl_;
};
#endif // CHROME_BROWSER_METRICS_POWER_POWER_METRICS_PROVIDER_MAC_H_

@ -4,25 +4,23 @@
#include "chrome/browser/metrics/power/power_metrics_provider_mac.h"
#import <Foundation/Foundation.h>
#include <memory>
#include <utility>
#import <Foundation/Foundation.h>
#include <libkern/OSByteOrder.h>
#include "base/bind.h"
#include "base/callback.h"
#include "base/mac/scoped_ioobject.h"
#include "base/memory/scoped_refptr.h"
#include "base/location.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/power_monitor/power_monitor.h"
#include "base/process/process.h"
#include "base/task/post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/strings/strcat.h"
#include "base/strings/string_piece.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "chrome/browser/ui/browser_finder.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "base/timer/timer.h"
#include "components/power_metrics/smc_mac.h"
namespace {
constexpr base::TimeDelta kStartupPowerMetricsCollectionDuration =
@ -32,145 +30,6 @@ constexpr base::TimeDelta kStartupPowerMetricsCollectionInterval =
constexpr base::TimeDelta kPostStartupPowerMetricsCollectionInterval =
base::Seconds(60);
// This API is undocumented. It can read hardware sensors including
// temperature, voltage, and power. A useful tool for discovering new keys is
// <https://github.com/theopolis/smc-fuzzer>. The following definitions are
// from
// <https://opensource.apple.com/source/PowerManagement/PowerManagement-271.1.1/pmconfigd/PrivateLib.c.auto.html>.
struct SMCParamStruct {
enum {
kSMCUserClientOpen = 0,
kSMCUserClientClose = 1,
kSMCHandleYPCEvent = 2,
kSMCReadKey = 5,
kSMCGetKeyInfo = 9,
};
enum class SMCKey : uint32_t {
TotalPower = 'PSTR', // Power: System Total Rail (watts)
CPUPower = 'PCPC', // Power: CPU Package CPU (watts)
iGPUPower = 'PCPG', // Power: CPU Package GPU (watts)
GPU0Power = 'PG0R', // Power: GPU 0 Rail (watts)
GPU1Power = 'PG1R', // Power: GPU 1 Rail (watts)
};
// SMC keys are typed, and there are a number of numeric types. Support for
// decoding the ones in this enum is implemented below, but there are more
// types (and more may appear in future hardware). Implement as needed.
enum class DataType : uint32_t {
flt = 'flt ', // Floating point
sp78 = 'sp78', // Fixed point: SIIIIIIIFFFFFFFF
sp87 = 'sp87', // Fixed point: SIIIIIIIIFFFFFFF
spa5 = 'spa5', // Fixed point: SIIIIIIIIIIFFFFF
};
struct SMCVersion {
unsigned char major;
unsigned char minor;
unsigned char build;
unsigned char reserved;
unsigned short release;
};
struct SMCPLimitData {
uint16_t version;
uint16_t length;
uint32_t cpuPLimit;
uint32_t gpuPLimit;
uint32_t memPLimit;
};
struct SMCKeyInfoData {
IOByteCount dataSize;
DataType dataType;
uint8_t dataAttributes;
};
SMCKey key;
SMCVersion vers;
SMCPLimitData pLimitData;
SMCKeyInfoData keyInfo;
uint8_t result;
uint8_t status;
uint8_t data8;
uint32_t data32;
uint8_t bytes[32];
};
float FromSMCFixedPoint(uint8_t* bytes, size_t fraction_bits) {
return static_cast<int16_t>(OSReadBigInt16(bytes, 0)) /
static_cast<float>(1 << fraction_bits);
}
class SMCKey {
public:
SMCKey(base::mac::ScopedIOObject<io_object_t> connect,
SMCParamStruct::SMCKey key)
: connect_(std::move(connect)), key_(key) {
SMCParamStruct out{};
if (CallSMCFunction(SMCParamStruct::kSMCGetKeyInfo, &out))
keyInfo_ = out.keyInfo;
}
bool Exists() { return keyInfo_.dataSize > 0; }
float Read() {
if (!Exists())
return 0;
SMCParamStruct out{};
if (!CallSMCFunction(SMCParamStruct::kSMCReadKey, &out))
return 0;
switch (keyInfo_.dataType) {
case SMCParamStruct::DataType::flt:
return *reinterpret_cast<float*>(out.bytes);
case SMCParamStruct::DataType::sp78:
return FromSMCFixedPoint(out.bytes, 8);
case SMCParamStruct::DataType::sp87:
return FromSMCFixedPoint(out.bytes, 7);
case SMCParamStruct::DataType::spa5:
return FromSMCFixedPoint(out.bytes, 5);
default:
break;
}
return 0;
}
private:
bool CallSMCFunction(uint8_t which, SMCParamStruct* out) {
if (!connect_)
return false;
if (IOConnectCallMethod(connect_, SMCParamStruct::kSMCUserClientOpen,
nullptr, 0, nullptr, 0, nullptr, nullptr, nullptr,
nullptr)) {
connect_.reset();
return false;
}
SMCParamStruct in{};
in.key = key_;
in.keyInfo.dataSize = keyInfo_.dataSize;
in.data8 = which;
size_t out_size = sizeof(*out);
bool success = IOConnectCallStructMethod(
connect_, SMCParamStruct::kSMCHandleYPCEvent, &in,
sizeof(in), out, &out_size) == kIOReturnSuccess;
if (IOConnectCallMethod(connect_, SMCParamStruct::kSMCUserClientClose,
nullptr, 0, nullptr, 0, nullptr, nullptr, nullptr,
nullptr))
connect_.reset();
// Even if the close failed, report whether the actual call succeded.
return success;
}
base::mac::ScopedIOObject<io_object_t> connect_;
SMCParamStruct::SMCKey key_;
SMCParamStruct::SMCKeyInfoData keyInfo_{};
};
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class ThermalStateUMA {
@ -194,34 +53,29 @@ ThermalStateUMA ThermalStateToUmaEnumValue(NSProcessInfoThermalState state) {
}
}
void RecordSMCHistogram(base::StringPiece prefix,
base::StringPiece suffix,
absl::optional<double> watts) {
if (watts.has_value()) {
double milliwatts = watts.value() * 1000;
base::UmaHistogramCounts100000(base::StrCat({prefix, suffix}), milliwatts);
}
}
} // namespace
class PowerMetricsProvider::Impl : public base::RefCountedThreadSafe<Impl> {
class PowerMetricsProvider::Impl {
public:
static scoped_refptr<Impl> Create(
base::mac::ScopedIOObject<io_object_t> connect) {
scoped_refptr<Impl> impl = new Impl(std::move(connect));
impl->ScheduleCollection();
return impl;
Impl() : smc_reader_(power_metrics::SMCReader::Create()) {
ScheduleCollection();
}
~Impl() = default;
Impl(const Impl&) = delete;
Impl& operator=(const Impl&) = delete;
private:
friend class base::RefCountedThreadSafe<Impl>;
Impl(base::mac::ScopedIOObject<io_object_t> connect)
: task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})),
system_total_power_key_(connect, SMCParamStruct::SMCKey::TotalPower),
cpu_package_cpu_power_key_(connect, SMCParamStruct::SMCKey::CPUPower),
cpu_package_gpu_power_key_(connect, SMCParamStruct::SMCKey::iGPUPower),
gpu_0_power_key_(connect, SMCParamStruct::SMCKey::GPU0Power),
gpu_1_power_key_(connect, SMCParamStruct::SMCKey::GPU0Power) {}
~Impl() = default;
bool IsInStartup() {
if (could_be_in_startup_) {
const base::TimeDelta process_uptime =
@ -233,10 +87,10 @@ class PowerMetricsProvider::Impl : public base::RefCountedThreadSafe<Impl> {
}
void ScheduleCollection() {
task_runner_->PostDelayedTask(
FROM_HERE, base::BindOnce(&Impl::Collect, this),
IsInStartup() ? kStartupPowerMetricsCollectionInterval
: kPostStartupPowerMetricsCollectionInterval);
timer_.Start(FROM_HERE,
IsInStartup() ? kStartupPowerMetricsCollectionInterval
: kPostStartupPowerMetricsCollectionInterval,
this, &Impl::Collect);
}
void Collect() {
@ -251,30 +105,27 @@ class PowerMetricsProvider::Impl : public base::RefCountedThreadSafe<Impl> {
}
}
void RecordSMC(const std::string& name) {
const struct {
const char* uma_prefix;
SMCKey& smc_key;
} sensors[] = {
{"Power.Mac.Total.", system_total_power_key_},
{"Power.Mac.CPU.", cpu_package_cpu_power_key_},
{"Power.Mac.GPUi.", cpu_package_gpu_power_key_},
{"Power.Mac.GPU0.", gpu_0_power_key_},
{"Power.Mac.GPU1.", gpu_1_power_key_},
};
for (const auto& sensor : sensors) {
if (sensor.smc_key.Exists()) {
if (auto power_mw = sensor.smc_key.Read() * 1000)
base::UmaHistogramCounts100000(sensor.uma_prefix + name, power_mw);
}
}
void RecordSMC(base::StringPiece suffix) {
if (!smc_reader_)
return;
RecordSMCHistogram("Power.Mac.Total.", suffix,
smc_reader_->ReadTotalPowerW());
RecordSMCHistogram("Power.Mac.CPU.", suffix,
smc_reader_->ReadCPUPackageCPUPowerW());
RecordSMCHistogram("Power.Mac.GPUi.", suffix,
smc_reader_->ReadCPUPackageGPUPowerW());
RecordSMCHistogram("Power.Mac.GPU0.", suffix,
smc_reader_->ReadGPU0PowerW());
RecordSMCHistogram("Power.Mac.GPU1.", suffix,
smc_reader_->ReadGPU1PowerW());
}
void RecordIsOnBattery() {
bool is_on_battery = false;
if (base::PowerMonitor::IsInitialized())
is_on_battery = base::PowerMonitor::IsOnBatteryPower();
UMA_HISTOGRAM_BOOLEAN("Power.Mac.IsOnBattery2", is_on_battery);
if (base::PowerMonitor::IsInitialized()) {
UMA_HISTOGRAM_BOOLEAN("Power.Mac.IsOnBattery2",
base::PowerMonitor::IsOnBatteryPower());
}
}
void RecordThermal() {
@ -283,30 +134,18 @@ class PowerMetricsProvider::Impl : public base::RefCountedThreadSafe<Impl> {
ThermalStateToUmaEnumValue([[NSProcessInfo processInfo] thermalState]));
}
scoped_refptr<base::SequencedTaskRunner> task_runner_;
base::OneShotTimer timer_;
std::unique_ptr<power_metrics::SMCReader> smc_reader_;
bool could_be_in_startup_ = true;
SMCKey system_total_power_key_;
SMCKey cpu_package_cpu_power_key_;
SMCKey cpu_package_gpu_power_key_;
SMCKey gpu_0_power_key_;
SMCKey gpu_1_power_key_;
};
PowerMetricsProvider::PowerMetricsProvider() = default;
PowerMetricsProvider::~PowerMetricsProvider() = default;
void PowerMetricsProvider::OnRecordingEnabled() {
const base::mac::ScopedIOObject<io_service_t> smc_service(
IOServiceGetMatchingService(kIOMasterPortDefault,
IOServiceMatching("AppleSMC")));
io_object_t connect;
bool service_opened = IOServiceOpen(smc_service, mach_task_self(), 1,
&connect) == kIOReturnSuccess;
UMA_HISTOGRAM_BOOLEAN("Power.Mac.AppleSMCOpened", service_opened);
if (!service_opened)
return;
impl_ = Impl::Create(base::mac::ScopedIOObject<io_object_t>(connect));
impl_ = base::SequenceBound<Impl>(base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}));
}
void PowerMetricsProvider::OnRecordingDisabled() {

@ -12,6 +12,9 @@ if (is_mac) {
"resource_coalition_internal_types_mac.h",
"resource_coalition_mac.h",
"resource_coalition_mac.mm",
"smc_internal_types_mac.h",
"smc_mac.h",
"smc_mac.mm",
]
deps = [ "//base" ]

@ -0,0 +1,74 @@
// 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_SMC_INTERNAL_TYPES_MAC_H_
#define COMPONENTS_POWER_METRICS_SMC_INTERNAL_TYPES_MAC_H_
#import <Foundation/Foundation.h>
#include <stdint.h>
// List of known SMC key identifiers.
enum class SMCKeyIdentifier : uint32_t {
TotalPower = 'PSTR', // Power: System Total Rail (watts)
CPUPower = 'PCPC', // Power: CPU Package CPU (watts)
iGPUPower = 'PCPG', // Power: CPU Package GPU (watts)
GPU0Power = 'PG0R', // Power: GPU 0 Rail (watts)
GPU1Power = 'PG1R', // Power: GPU 1 Rail (watts)
};
// Types from PowerManagement/pmconfigd/PrivateLib.c
// (https://opensource.apple.com/source/PowerManagement/PowerManagement-494.1.2/pmconfigd/PrivateLib.c.auto.html)
struct SMCVersion {
unsigned char major;
unsigned char minor;
unsigned char build;
unsigned char reserved;
unsigned short release;
};
struct SMCPLimitData {
uint16_t version;
uint16_t length;
uint32_t cpuPLimit;
uint32_t gpuPLimit;
uint32_t memPLimit;
};
enum class SMCDataType : uint32_t {
flt = 'flt ', // Floating point
sp78 = 'sp78', // Fixed point: SIIIIIIIFFFFFFFF
sp87 = 'sp87', // Fixed point: SIIIIIIIIFFFFFFF
spa5 = 'spa5', // Fixed point: SIIIIIIIIIIFFFFF
};
struct SMCKeyInfoData {
IOByteCount dataSize;
SMCDataType dataType;
uint8_t dataAttributes;
};
struct SMCParamStruct {
SMCKeyIdentifier key;
SMCVersion vers;
SMCPLimitData pLimitData;
SMCKeyInfoData keyInfo;
uint8_t result;
uint8_t status;
uint8_t data8;
uint32_t data32;
uint8_t bytes[32];
};
enum {
kSMCUserClientOpen = 0,
kSMCUserClientClose = 1,
kSMCHandleYPCEvent = 2,
kSMCReadKey = 5,
kSMCWriteKey = 6,
kSMCGetKeyCount = 7,
kSMCGetKeyFromIndex = 8,
kSMCGetKeyInfo = 9
};
#endif // COMPONENTS_POWER_METRICS_SMC_INTERNAL_TYPES_MAC_H_

@ -0,0 +1,67 @@
// 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.
// The System Management Controller (SMC) is a hardware component that controls
// the power functions of Intel-based Macs. This file defines a class to read
// known SMC keys.
#ifndef COMPONENTS_POWER_METRICS_SMC_MAC_H_
#define COMPONENTS_POWER_METRICS_SMC_MAC_H_
#import <Foundation/Foundation.h>
#include <memory>
#include "base/mac/scoped_ioobject.h"
#include "components/power_metrics/smc_internal_types_mac.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace power_metrics {
class SMCReader {
public:
// Creates an SMC Reader. Returns nullptr in case of failure.
static std::unique_ptr<SMCReader> Create();
virtual ~SMCReader();
// Returns the power consumption of various hardware components in watts.
// Virtual for testing.
virtual absl::optional<double> ReadTotalPowerW();
virtual absl::optional<double> ReadCPUPackageCPUPowerW();
virtual absl::optional<double> ReadCPUPackageGPUPowerW();
virtual absl::optional<double> ReadGPU0PowerW();
virtual absl::optional<double> ReadGPU1PowerW();
protected:
explicit SMCReader(base::mac::ScopedIOObject<io_object_t> connect);
private:
class SMCKey {
public:
SMCKey(base::mac::ScopedIOObject<io_object_t> connect,
SMCKeyIdentifier key_identifier);
~SMCKey();
bool Exists() const;
absl::optional<double> Read();
private:
bool CallSMCFunction(uint8_t function, SMCParamStruct* out);
base::mac::ScopedIOObject<io_object_t> connect_;
const SMCKeyIdentifier key_identifier_;
SMCKeyInfoData key_info_;
};
SMCKey total_power_key_;
SMCKey cpu_package_cpu_power_key_;
SMCKey cpu_package_gpu_power_key_;
SMCKey gpu0_power_key_;
SMCKey gpu1_power_key_;
};
} // namespace power_metrics
#endif // COMPONENTS_POWER_METRICS_SMC_MAC_H_

@ -0,0 +1,134 @@
// 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/smc_mac.h"
#include <libkern/OSByteOrder.h>
#include <utility>
#include "base/memory/ptr_util.h"
namespace power_metrics {
namespace {
double FromSMCFixedPoint(uint8_t* bytes, size_t fraction_bits) {
return OSReadBigInt16(bytes, 0) / static_cast<double>(1 << fraction_bits);
}
} // namespace
// static
std::unique_ptr<SMCReader> SMCReader::Create() {
const base::mac::ScopedIOObject<io_service_t> smc_service(
IOServiceGetMatchingService(kIOMasterPortDefault,
IOServiceMatching("AppleSMC")));
base::mac::ScopedIOObject<io_object_t> connect;
if (IOServiceOpen(smc_service, mach_task_self(), 1,
connect.InitializeInto()) != kIOReturnSuccess) {
return nullptr;
}
return base::WrapUnique(new SMCReader(std::move(connect)));
}
SMCReader::~SMCReader() = default;
absl::optional<double> SMCReader::ReadTotalPowerW() {
return total_power_key_.Read();
}
absl::optional<double> SMCReader::ReadCPUPackageCPUPowerW() {
return cpu_package_cpu_power_key_.Read();
}
absl::optional<double> SMCReader::ReadCPUPackageGPUPowerW() {
return cpu_package_gpu_power_key_.Read();
}
absl::optional<double> SMCReader::ReadGPU0PowerW() {
return gpu0_power_key_.Read();
}
absl::optional<double> SMCReader::ReadGPU1PowerW() {
return gpu1_power_key_.Read();
}
SMCReader::SMCKey::SMCKey(base::mac::ScopedIOObject<io_object_t> connect,
SMCKeyIdentifier key_identifier)
: connect_(std::move(connect)), key_identifier_(key_identifier) {
// Read key information.
SMCParamStruct out{};
if (CallSMCFunction(kSMCGetKeyInfo, &out))
key_info_ = out.keyInfo;
}
SMCReader::SMCKey::~SMCKey() = default;
bool SMCReader::SMCKey::Exists() const {
return key_info_.dataSize > 0;
}
absl::optional<double> SMCReader::SMCKey::Read() {
if (!Exists())
return absl::nullopt;
SMCParamStruct out{};
if (!CallSMCFunction(kSMCReadKey, &out))
return absl::nullopt;
switch (key_info_.dataType) {
case SMCDataType::flt:
return *reinterpret_cast<float*>(out.bytes);
case SMCDataType::sp78:
return FromSMCFixedPoint(out.bytes, 8);
case SMCDataType::sp87:
return FromSMCFixedPoint(out.bytes, 7);
case SMCDataType::spa5:
return FromSMCFixedPoint(out.bytes, 5);
default:
return absl::nullopt;
}
}
bool SMCReader::SMCKey::CallSMCFunction(uint8_t function, SMCParamStruct* out) {
if (!connect_)
return false;
// TODO: In local tests, removing the calls to `kSMCUserClientOpen` and
// `kSMCUserClientClose` doesn't seem to affect behavior. Consider removing
// them.
if (IOConnectCallMethod(connect_, kSMCUserClientOpen, nullptr, 0, nullptr, 0,
nullptr, nullptr, nullptr, nullptr)) {
connect_.reset();
return false;
}
SMCParamStruct in{};
in.key = key_identifier_;
in.keyInfo.dataSize = key_info_.dataSize;
in.data8 = function;
size_t out_size = sizeof(*out);
const bool success =
IOConnectCallStructMethod(connect_, kSMCHandleYPCEvent, &in, sizeof(in),
out, &out_size) == kIOReturnSuccess;
if (IOConnectCallMethod(connect_, kSMCUserClientClose, nullptr, 0, nullptr, 0,
nullptr, nullptr, nullptr, nullptr)) {
connect_.reset();
}
// Even if the close failed, report whether the actual call succeded.
return success;
}
SMCReader::SMCReader(base::mac::ScopedIOObject<io_object_t> connect)
: total_power_key_(connect, SMCKeyIdentifier::TotalPower),
cpu_package_cpu_power_key_(connect, SMCKeyIdentifier::CPUPower),
cpu_package_gpu_power_key_(connect, SMCKeyIdentifier::iGPUPower),
gpu0_power_key_(connect, SMCKeyIdentifier::GPU0Power),
gpu1_power_key_(connect, SMCKeyIdentifier::GPU1Power) {}
} // namespace power_metrics

@ -47,6 +47,8 @@ static_library("power_sampler_lib") {
"power_sampler/sampling_controller.h",
"power_sampler/sampling_event_source.cc",
"power_sampler/sampling_event_source.h",
"power_sampler/smc_sampler.h",
"power_sampler/smc_sampler.mm",
"power_sampler/timer_sampling_event_source.cc",
"power_sampler/timer_sampling_event_source.h",
"power_sampler/user_idle_level_sampler.cc",
@ -84,6 +86,7 @@ test("power_sampler_unittests") {
"power_sampler/main_display_sampler_unittest.cc",
"power_sampler/resource_coalition_sampler_unittest.cc",
"power_sampler/sampling_controller_unittest.cc",
"power_sampler/smc_sampler_unittest.mm",
"power_sampler/timer_sampling_event_source_unittest.cc",
"power_sampler/user_idle_level_sampler_unittest.cc",
]

@ -23,6 +23,7 @@
#include "tools/mac/power/power_sampler/sample_counter.h"
#include "tools/mac/power/power_sampler/sampler.h"
#include "tools/mac/power/power_sampler/sampling_controller.h"
#include "tools/mac/power/power_sampler/smc_sampler.h"
#include "tools/mac/power/power_sampler/timer_sampling_event_source.h"
#include "tools/mac/power/power_sampler/user_idle_level_sampler.h"
@ -154,6 +155,10 @@ int main(int argc, char** argv) {
if (sampler)
controller.AddSampler(std::move(sampler));
sampler = power_sampler::SMCSampler::Create();
if (sampler)
controller.AddSampler(std::move(sampler));
sampler = power_sampler::UserIdleLevelSampler::Create();
if (sampler)
controller.AddSampler(std::move(sampler));

@ -0,0 +1,43 @@
// 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 TOOLS_MAC_POWER_POWER_SAMPLER_SMC_SAMPLER_H_
#define TOOLS_MAC_POWER_POWER_SAMPLER_SMC_SAMPLER_H_
#include <memory>
#include "tools/mac/power/power_sampler/sampler.h"
namespace power_metrics {
class SMCReader;
}
namespace power_sampler {
// The SMC sampler samples power usage from various hardware components from the
// System Management Controller (SMC).
class SMCSampler : public Sampler {
public:
~SMCSampler() override;
// Creates and initializes a new sampler, if possible.
// Returns nullptr on failure.
static std::unique_ptr<SMCSampler> Create();
// Sampler implementation.
std::string GetName() override;
DatumNameUnits GetDatumNameUnits() override;
Sample GetSample(base::TimeTicks sample_time) override;
private:
friend class SMCSamplerTest;
SMCSampler(std::unique_ptr<power_metrics::SMCReader> smc_reader);
std::unique_ptr<power_metrics::SMCReader> smc_reader_;
};
} // namespace power_sampler
#endif // TOOLS_MAC_POWER_POWER_SAMPLER_BATTERY_SAMPLER_H_

@ -0,0 +1,67 @@
// 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 "tools/mac/power/power_sampler/smc_sampler.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_piece.h"
#include "components/power_metrics/smc_mac.h"
namespace power_sampler {
namespace {
void MaybeAddToSample(Sampler::Sample* sample,
base::StringPiece name,
absl::optional<double> val) {
if (val.has_value())
sample->emplace(name, val.value());
}
} // namespace
SMCSampler::~SMCSampler() = default;
// static
std::unique_ptr<SMCSampler> SMCSampler::Create() {
std::unique_ptr<power_metrics::SMCReader> smc_reader =
power_metrics::SMCReader::Create();
if (!smc_reader)
return nullptr;
return base::WrapUnique(new SMCSampler(std::move(smc_reader)));
}
std::string SMCSampler::GetName() {
return "smc";
}
Sampler::DatumNameUnits SMCSampler::GetDatumNameUnits() {
DatumNameUnits ret{{"total_power", "w"},
{"cpu_package_cpu_power", "w"},
{"cpu_package_gpu_power", "w"},
{"gpu0_power", "w"},
{"gpu1_power", "w"}};
return ret;
}
Sampler::Sample SMCSampler::GetSample(base::TimeTicks sample_time) {
Sample sample;
MaybeAddToSample(&sample, "total_power", smc_reader_->ReadTotalPowerW());
MaybeAddToSample(&sample, "cpu_package_cpu_power",
smc_reader_->ReadCPUPackageCPUPowerW());
MaybeAddToSample(&sample, "cpu_package_gpu_power",
smc_reader_->ReadCPUPackageGPUPowerW());
MaybeAddToSample(&sample, "gpu0_power", smc_reader_->ReadGPU0PowerW());
MaybeAddToSample(&sample, "gpu1_power", smc_reader_->ReadGPU1PowerW());
return sample;
}
SMCSampler::SMCSampler(std::unique_ptr<power_metrics::SMCReader> smc_reader)
: smc_reader_(std::move(smc_reader)) {
DCHECK(smc_reader_);
}
} // namespace power_sampler

@ -0,0 +1,166 @@
// 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 "tools/mac/power/power_sampler/smc_sampler.h"
#include <memory>
#include "base/memory/ptr_util.h"
#include "components/power_metrics/smc_mac.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "tools/mac/power/power_sampler/battery_sampler.h"
namespace power_sampler {
namespace {
using testing::UnorderedElementsAre;
class TestSMCReader : public power_metrics::SMCReader {
public:
TestSMCReader()
: power_metrics::SMCReader(base::mac::ScopedIOObject<io_object_t>()) {}
void set_total_power(absl::optional<double> total_power) {
total_power_ = total_power;
}
void set_cpu_package_cpu_power(absl::optional<double> cpu_package_cpu_power) {
cpu_package_cpu_power_ = cpu_package_cpu_power;
}
void set_cpu_package_gpu_power(absl::optional<double> cpu_package_gpu_power) {
cpu_package_gpu_power_ = cpu_package_gpu_power;
}
void set_gpu0_power(absl::optional<double> gpu0_power) {
gpu0_power_ = gpu0_power;
}
void set_gpu1_power(absl::optional<double> gpu1_power) {
gpu1_power_ = gpu1_power;
}
// power_metrics::SMCReader:
absl::optional<double> ReadTotalPowerW() override { return total_power_; }
absl::optional<double> ReadCPUPackageCPUPowerW() override {
return cpu_package_cpu_power_;
}
absl::optional<double> ReadCPUPackageGPUPowerW() override {
return cpu_package_gpu_power_;
}
absl::optional<double> ReadGPU0PowerW() override { return gpu0_power_; }
absl::optional<double> ReadGPU1PowerW() override { return gpu1_power_; }
private:
absl::optional<double> total_power_;
absl::optional<double> cpu_package_cpu_power_;
absl::optional<double> cpu_package_gpu_power_;
absl::optional<double> gpu0_power_;
absl::optional<double> gpu1_power_;
};
} // namespace
class SMCSamplerTest : public testing::Test {
public:
SMCSamplerTest() {
std::unique_ptr<TestSMCReader> reader = std::make_unique<TestSMCReader>();
reader_ = reader.get();
sampler_ = base::WrapUnique(new SMCSampler(std::move(reader)));
}
TestSMCReader* reader_ = nullptr;
std::unique_ptr<SMCSampler> sampler_;
};
TEST_F(SMCSamplerTest, NameAndGetDatumNameUnits) {
EXPECT_EQ("smc", sampler_->GetName());
auto datum_name_units = sampler_->GetDatumNameUnits();
EXPECT_THAT(datum_name_units,
UnorderedElementsAre(std::make_pair("total_power", "w"),
std::make_pair("cpu_package_cpu_power", "w"),
std::make_pair("cpu_package_gpu_power", "w"),
std::make_pair("gpu0_power", "w"),
std::make_pair("gpu1_power", "w")));
}
TEST_F(SMCSamplerTest, GetSample_AllFieldsAvailable) {
reader_->set_total_power(1);
reader_->set_cpu_package_cpu_power(2);
reader_->set_cpu_package_gpu_power(3);
reader_->set_gpu0_power(4);
reader_->set_gpu1_power(5);
Sampler::Sample sample = sampler_->GetSample(base::TimeTicks());
EXPECT_THAT(sample,
UnorderedElementsAre(std::make_pair("total_power", 1),
std::make_pair("cpu_package_cpu_power", 2),
std::make_pair("cpu_package_gpu_power", 3),
std::make_pair("gpu0_power", 4),
std::make_pair("gpu1_power", 5)));
}
TEST_F(SMCSamplerTest, GetSample_IndividualFieldNotAvailable) {
reader_->set_total_power(1);
reader_->set_cpu_package_cpu_power(2);
reader_->set_cpu_package_gpu_power(3);
reader_->set_gpu0_power(4);
reader_->set_gpu1_power(5);
{
reader_->set_total_power(absl::nullopt);
Sampler::Sample sample = sampler_->GetSample(base::TimeTicks());
EXPECT_THAT(sample,
UnorderedElementsAre(std::make_pair("cpu_package_cpu_power", 2),
std::make_pair("cpu_package_gpu_power", 3),
std::make_pair("gpu0_power", 4),
std::make_pair("gpu1_power", 5)));
reader_->set_total_power(1);
}
{
reader_->set_cpu_package_cpu_power(absl::nullopt);
Sampler::Sample sample = sampler_->GetSample(base::TimeTicks());
EXPECT_THAT(sample,
UnorderedElementsAre(std::make_pair("total_power", 1),
std::make_pair("cpu_package_gpu_power", 3),
std::make_pair("gpu0_power", 4),
std::make_pair("gpu1_power", 5)));
reader_->set_cpu_package_cpu_power(2);
}
{
reader_->set_cpu_package_gpu_power(absl::nullopt);
Sampler::Sample sample = sampler_->GetSample(base::TimeTicks());
EXPECT_THAT(sample,
UnorderedElementsAre(std::make_pair("total_power", 1),
std::make_pair("cpu_package_cpu_power", 2),
std::make_pair("gpu0_power", 4),
std::make_pair("gpu1_power", 5)));
reader_->set_cpu_package_gpu_power(3);
}
{
reader_->set_gpu0_power(absl::nullopt);
Sampler::Sample sample = sampler_->GetSample(base::TimeTicks());
EXPECT_THAT(sample,
UnorderedElementsAre(std::make_pair("total_power", 1),
std::make_pair("cpu_package_cpu_power", 2),
std::make_pair("cpu_package_gpu_power", 3),
std::make_pair("gpu1_power", 5)));
reader_->set_gpu0_power(4);
}
{
reader_->set_gpu1_power(absl::nullopt);
Sampler::Sample sample = sampler_->GetSample(base::TimeTicks());
EXPECT_THAT(sample,
UnorderedElementsAre(std::make_pair("total_power", 1),
std::make_pair("cpu_package_cpu_power", 2),
std::make_pair("cpu_package_gpu_power", 3),
std::make_pair("gpu0_power", 4)));
reader_->set_gpu1_power(5);
}
}
} // namespace power_sampler