[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
@ -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
|
Reference in New Issue
Block a user