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

@ -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