[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:

committed by
Chromium LUCI CQ

parent
833a36e2b2
commit
9b7f7b5b80
chrome/browser/metrics/power
components/power_metrics
tools/mac/power
@ -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" ]
|
||||
|
74
components/power_metrics/smc_internal_types_mac.h
Normal file
74
components/power_metrics/smc_internal_types_mac.h
Normal file
@ -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_
|
67
components/power_metrics/smc_mac.h
Normal file
67
components/power_metrics/smc_mac.h
Normal file
@ -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_
|
134
components/power_metrics/smc_mac.mm
Normal file
134
components/power_metrics/smc_mac.mm
Normal file
@ -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));
|
||||
|
43
tools/mac/power/power_sampler/smc_sampler.h
Normal file
43
tools/mac/power/power_sampler/smc_sampler.h
Normal file
@ -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_
|
67
tools/mac/power/power_sampler/smc_sampler.mm
Normal file
67
tools/mac/power/power_sampler/smc_sampler.mm
Normal file
@ -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
|
166
tools/mac/power/power_sampler/smc_sampler_unittest.mm
Normal file
166
tools/mac/power/power_sampler/smc_sampler_unittest.mm
Normal file
@ -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
|
Reference in New Issue
Block a user