[power_sampler] Add sampling of M1 temperature sensors.
Bug: 1254332 Change-Id: Ie55c0792d0afff7dabe2939f3111a3ab23bbf76a Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3323152 Reviewed-by: Etienne Pierre-Doray <etiennep@chromium.org> Reviewed-by: Jayson Adams <shrike@chromium.org> Commit-Queue: Francois Pierre Doray <fdoray@chromium.org> Cr-Commit-Position: refs/heads/main@{#956644}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
5d5d48d6be
commit
30385a7022
components/power_metrics
tools/mac/power
@ -9,6 +9,9 @@ if (is_mac) {
|
||||
"energy_impact_mac.mm",
|
||||
"iopm_power_source_sampling_event_source.cc",
|
||||
"iopm_power_source_sampling_event_source.h",
|
||||
"m1_sensors_internal_types_mac.h",
|
||||
"m1_sensors_mac.h",
|
||||
"m1_sensors_mac.mm",
|
||||
"mach_time_mac.h",
|
||||
"mach_time_mac.mm",
|
||||
"resource_coalition_internal_types_mac.h",
|
||||
|
26
components/power_metrics/m1_sensors_internal_types_mac.h
Normal file
26
components/power_metrics/m1_sensors_internal_types_mac.h
Normal file
@ -0,0 +1,26 @@
|
||||
// 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_M1_SENSORS_INTERNAL_TYPES_MAC_H_
|
||||
#define COMPONENTS_POWER_METRICS_M1_SENSORS_INTERNAL_TYPES_MAC_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// From:
|
||||
// https://opensource.apple.com/source/IOHIDFamily/IOHIDFamily-421.6/IOHIDFamily/IOHIDEventTypes.h.auto.html
|
||||
|
||||
#define IOHIDEventFieldBase(type) (type << 16)
|
||||
|
||||
constexpr int64_t kIOHIDEventTypeTemperature = 15;
|
||||
|
||||
// From:
|
||||
// https://opensource.apple.com/source/IOHIDFamily/IOHIDFamily-421.6/IOHIDFamily/AppleHIDUsageTables.h
|
||||
|
||||
// Usage pages
|
||||
constexpr int kHIDPage_AppleVendor = 0xff00;
|
||||
|
||||
// Usage keys for `kHIDPage_AppleVendor`
|
||||
constexpr int kHIDUsage_AppleVendor_TemperatureSensor = 0x0005;
|
||||
|
||||
#endif // COMPONENTS_POWER_METRICS_M1_SENSORS_INTERNAL_TYPES_MAC_H_
|
48
components/power_metrics/m1_sensors_mac.h
Normal file
48
components/power_metrics/m1_sensors_mac.h
Normal file
@ -0,0 +1,48 @@
|
||||
// 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 Apple M1 chip has sensors to monitor its power consumption and
|
||||
// temperature. This file defines a class to retrieve data from these sensors.
|
||||
|
||||
#ifndef COMPONENTS_POWER_METRICS_M1_SENSORS_MAC_H_
|
||||
#define COMPONENTS_POWER_METRICS_M1_SENSORS_MAC_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <IOKit/hidsystem/IOHIDEventSystemClient.h>
|
||||
|
||||
#include "base/mac/scoped_cftyperef.h"
|
||||
#include "third_party/abseil-cpp/absl/types/optional.h"
|
||||
|
||||
namespace power_metrics {
|
||||
|
||||
class M1SensorsReader {
|
||||
public:
|
||||
struct TemperaturesCelsius {
|
||||
TemperaturesCelsius();
|
||||
TemperaturesCelsius(const TemperaturesCelsius&) noexcept;
|
||||
~TemperaturesCelsius();
|
||||
|
||||
absl::optional<double> p_cores;
|
||||
absl::optional<double> e_cores;
|
||||
};
|
||||
|
||||
virtual ~M1SensorsReader();
|
||||
|
||||
// Creates an M1SensorsReader. Returns nullptr on failure.
|
||||
static std::unique_ptr<M1SensorsReader> Create();
|
||||
|
||||
// Reads temperature sensors. Virtual for testing.
|
||||
virtual TemperaturesCelsius ReadTemperatures();
|
||||
|
||||
protected:
|
||||
M1SensorsReader(base::ScopedCFTypeRef<IOHIDEventSystemClientRef> system);
|
||||
|
||||
private:
|
||||
base::ScopedCFTypeRef<IOHIDEventSystemClientRef> system_;
|
||||
};
|
||||
|
||||
} // namespace power_metrics
|
||||
|
||||
#endif // COMPONENTS_POWER_METRICS_M1_SENSORS_MAC_H_
|
123
components/power_metrics/m1_sensors_mac.mm
Normal file
123
components/power_metrics/m1_sensors_mac.mm
Normal file
@ -0,0 +1,123 @@
|
||||
// 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/m1_sensors_mac.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <IOKit/hid/IOHIDDeviceKeys.h>
|
||||
#import <IOKit/hidsystem/IOHIDServiceClient.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "base/mac/foundation_util.h"
|
||||
#include "base/memory/ptr_util.h"
|
||||
#include "components/power_metrics/m1_sensors_internal_types_mac.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
extern IOHIDEventSystemClientRef IOHIDEventSystemClientCreate(CFAllocatorRef);
|
||||
extern int IOHIDEventSystemClientSetMatching(IOHIDEventSystemClientRef client,
|
||||
CFDictionaryRef match);
|
||||
extern CFTypeRef IOHIDServiceClientCopyEvent(IOHIDServiceClientRef,
|
||||
int64_t,
|
||||
int32_t,
|
||||
int64_t);
|
||||
extern double IOHIDEventGetFloatValue(CFTypeRef, int32_t);
|
||||
}
|
||||
|
||||
namespace power_metrics {
|
||||
|
||||
namespace {
|
||||
|
||||
absl::optional<double> GetEventFloatValue(IOHIDServiceClientRef service,
|
||||
int64_t event_type) {
|
||||
base::ScopedCFTypeRef<CFTypeRef> event(
|
||||
IOHIDServiceClientCopyEvent(service, event_type, 0, 0));
|
||||
if (!event)
|
||||
return absl::nullopt;
|
||||
return IOHIDEventGetFloatValue(event, IOHIDEventFieldBase(event_type));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
M1SensorsReader::TemperaturesCelsius::TemperaturesCelsius() = default;
|
||||
M1SensorsReader::TemperaturesCelsius::TemperaturesCelsius(
|
||||
const TemperaturesCelsius&) noexcept = default;
|
||||
M1SensorsReader::TemperaturesCelsius::~TemperaturesCelsius() = default;
|
||||
|
||||
M1SensorsReader::~M1SensorsReader() = default;
|
||||
|
||||
// static
|
||||
std::unique_ptr<M1SensorsReader> M1SensorsReader::Create() {
|
||||
base::ScopedCFTypeRef<IOHIDEventSystemClientRef> system(
|
||||
IOHIDEventSystemClientCreate(kCFAllocatorDefault));
|
||||
|
||||
if (system == nil)
|
||||
return nullptr;
|
||||
|
||||
NSDictionary* filter = @{
|
||||
@kIOHIDPrimaryUsagePageKey : [NSNumber numberWithInt:kHIDPage_AppleVendor],
|
||||
@kIOHIDPrimaryUsageKey :
|
||||
[NSNumber numberWithInt:kHIDUsage_AppleVendor_TemperatureSensor],
|
||||
};
|
||||
IOHIDEventSystemClientSetMatching(system, base::mac::NSToCFCast(filter));
|
||||
|
||||
return base::WrapUnique(new M1SensorsReader(std::move(system)));
|
||||
}
|
||||
|
||||
M1SensorsReader::TemperaturesCelsius M1SensorsReader::ReadTemperatures() {
|
||||
base::ScopedCFTypeRef<CFArrayRef> services(
|
||||
IOHIDEventSystemClientCopyServices(system_.get()));
|
||||
|
||||
// There are multiple temperature sensors on P-Cores and E-Cores. Count and
|
||||
// sum values to compute average later.
|
||||
int num_p_core_temp = 0;
|
||||
int num_e_core_temp = 0;
|
||||
double sum_p_core_temp = 0;
|
||||
double sum_e_core_temp = 0;
|
||||
|
||||
for (id service_obj in base::mac::CFToNSCast(services.get())) {
|
||||
IOHIDServiceClientRef service = (IOHIDServiceClientRef)service_obj;
|
||||
|
||||
base::ScopedCFTypeRef<CFStringRef> product_cf(
|
||||
base::mac::CFCast<CFStringRef>(
|
||||
IOHIDServiceClientCopyProperty(service, CFSTR(kIOHIDProductKey))));
|
||||
if (product_cf == nil)
|
||||
continue;
|
||||
|
||||
if ([base::mac::CFToNSCast(product_cf.get())
|
||||
hasPrefix:@"pACC MTR Temp Sensor"]) {
|
||||
absl::optional<double> temp =
|
||||
GetEventFloatValue(service, kIOHIDEventTypeTemperature);
|
||||
if (temp.has_value()) {
|
||||
num_p_core_temp += 1;
|
||||
sum_p_core_temp += temp.value();
|
||||
}
|
||||
}
|
||||
|
||||
if ([base::mac::CFToNSCast(product_cf.get())
|
||||
hasPrefix:@"eACC MTR Temp Sensor"]) {
|
||||
absl::optional<double> temp =
|
||||
GetEventFloatValue(service, kIOHIDEventTypeTemperature);
|
||||
if (temp.has_value()) {
|
||||
num_e_core_temp += 1;
|
||||
sum_e_core_temp += temp.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TemperaturesCelsius temperatures;
|
||||
if (num_p_core_temp > 0)
|
||||
temperatures.p_cores = sum_p_core_temp / num_p_core_temp;
|
||||
if (num_e_core_temp > 0)
|
||||
temperatures.e_cores = sum_e_core_temp / num_e_core_temp;
|
||||
|
||||
return temperatures;
|
||||
}
|
||||
|
||||
M1SensorsReader::M1SensorsReader(
|
||||
base::ScopedCFTypeRef<IOHIDEventSystemClientRef> system)
|
||||
: system_(std::move(system)) {}
|
||||
|
||||
} // namespace power_metrics
|
@ -31,6 +31,8 @@ static_library("power_sampler_lib") {
|
||||
"power_sampler/csv_exporter.h",
|
||||
"power_sampler/json_exporter.cc",
|
||||
"power_sampler/json_exporter.h",
|
||||
"power_sampler/m1_sampler.h",
|
||||
"power_sampler/m1_sampler.mm",
|
||||
"power_sampler/main_display_sampler.cc",
|
||||
"power_sampler/main_display_sampler.h",
|
||||
"power_sampler/monitor.cc",
|
||||
@ -78,6 +80,7 @@ test("power_sampler_unittests") {
|
||||
"power_sampler/battery_sampler_unittest.cc",
|
||||
"power_sampler/csv_exporter_unittest.cc",
|
||||
"power_sampler/json_exporter_unittest.cc",
|
||||
"power_sampler/m1_sampler_unittest.mm",
|
||||
"power_sampler/main_display_sampler_unittest.cc",
|
||||
"power_sampler/resource_coalition_sampler_unittest.cc",
|
||||
"power_sampler/sampling_controller_unittest.cc",
|
||||
|
44
tools/mac/power/power_sampler/m1_sampler.h
Normal file
44
tools/mac/power/power_sampler/m1_sampler.h
Normal file
@ -0,0 +1,44 @@
|
||||
// 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_M1_SAMPLER_H_
|
||||
#define TOOLS_MAC_POWER_POWER_SAMPLER_M1_SAMPLER_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "tools/mac/power/power_sampler/sampler.h"
|
||||
|
||||
namespace power_metrics {
|
||||
class M1SensorsReader;
|
||||
}
|
||||
|
||||
namespace power_sampler {
|
||||
|
||||
// The M1 sensors sampler samples the temperature of M1 P-Cores and E-Cores.
|
||||
class M1Sampler : public Sampler {
|
||||
public:
|
||||
static constexpr char kSamplerName[] = "m1";
|
||||
|
||||
~M1Sampler() override;
|
||||
|
||||
// Creates and initializes a new sampler, if possible.
|
||||
// Returns nullptr on failure.
|
||||
static std::unique_ptr<M1Sampler> Create();
|
||||
|
||||
// Sampler implementation.
|
||||
std::string GetName() override;
|
||||
DatumNameUnits GetDatumNameUnits() override;
|
||||
Sample GetSample(base::TimeTicks sample_time) override;
|
||||
|
||||
private:
|
||||
friend class M1SamplerTest;
|
||||
|
||||
M1Sampler(std::unique_ptr<power_metrics::M1SensorsReader> reader);
|
||||
|
||||
std::unique_ptr<power_metrics::M1SensorsReader> reader_;
|
||||
};
|
||||
|
||||
} // namespace power_sampler
|
||||
|
||||
#endif // TOOLS_MAC_POWER_POWER_SAMPLER_M1_SAMPLER_H_
|
61
tools/mac/power/power_sampler/m1_sampler.mm
Normal file
61
tools/mac/power/power_sampler/m1_sampler.mm
Normal file
@ -0,0 +1,61 @@
|
||||
// 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/m1_sampler.h"
|
||||
|
||||
#include "base/memory/ptr_util.h"
|
||||
#include "base/strings/string_piece.h"
|
||||
#include "components/power_metrics/m1_sensors_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
|
||||
|
||||
M1Sampler::~M1Sampler() = default;
|
||||
|
||||
// static
|
||||
std::unique_ptr<M1Sampler> M1Sampler::Create() {
|
||||
std::unique_ptr<power_metrics::M1SensorsReader> reader =
|
||||
power_metrics::M1SensorsReader::Create();
|
||||
if (!reader)
|
||||
return nullptr;
|
||||
return base::WrapUnique(new M1Sampler(std::move(reader)));
|
||||
}
|
||||
|
||||
std::string M1Sampler::GetName() {
|
||||
return kSamplerName;
|
||||
}
|
||||
|
||||
Sampler::DatumNameUnits M1Sampler::GetDatumNameUnits() {
|
||||
DatumNameUnits ret{{"p_cores_temperature", "C"},
|
||||
{"e_cores_temperature", "C"}};
|
||||
return ret;
|
||||
}
|
||||
|
||||
Sampler::Sample M1Sampler::GetSample(base::TimeTicks sample_time) {
|
||||
Sample sample;
|
||||
power_metrics::M1SensorsReader::TemperaturesCelsius temperatures =
|
||||
reader_->ReadTemperatures();
|
||||
|
||||
MaybeAddToSample(&sample, "p_cores_temperature", temperatures.p_cores);
|
||||
MaybeAddToSample(&sample, "e_cores_temperature", temperatures.e_cores);
|
||||
|
||||
return sample;
|
||||
}
|
||||
|
||||
M1Sampler::M1Sampler(std::unique_ptr<power_metrics::M1SensorsReader> reader)
|
||||
: reader_(std::move(reader)) {
|
||||
DCHECK(reader_);
|
||||
}
|
||||
|
||||
} // namespace power_sampler
|
100
tools/mac/power/power_sampler/m1_sampler_unittest.mm
Normal file
100
tools/mac/power/power_sampler/m1_sampler_unittest.mm
Normal file
@ -0,0 +1,100 @@
|
||||
// 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/m1_sampler.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "base/containers/flat_map.h"
|
||||
#include "base/memory/ptr_util.h"
|
||||
#include "components/power_metrics/m1_sensors_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 TestM1SensorsReader : public power_metrics::M1SensorsReader {
|
||||
public:
|
||||
TestM1SensorsReader()
|
||||
: power_metrics::M1SensorsReader(
|
||||
base::ScopedCFTypeRef<IOHIDEventSystemClientRef>()) {}
|
||||
|
||||
void set_temperatures(TemperaturesCelsius temperatures) {
|
||||
temperatures_ = temperatures;
|
||||
}
|
||||
|
||||
// power_metrics::M1SensorsReader:
|
||||
TemperaturesCelsius ReadTemperatures() override { return temperatures_; }
|
||||
|
||||
private:
|
||||
TemperaturesCelsius temperatures_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
class M1SamplerTest : public testing::Test {
|
||||
public:
|
||||
M1SamplerTest() {
|
||||
std::unique_ptr<TestM1SensorsReader> reader =
|
||||
std::make_unique<TestM1SensorsReader>();
|
||||
reader_ = reader.get();
|
||||
sampler_ = base::WrapUnique(new M1Sampler(std::move(reader)));
|
||||
}
|
||||
|
||||
TestM1SensorsReader* reader_ = nullptr;
|
||||
std::unique_ptr<M1Sampler> sampler_;
|
||||
};
|
||||
|
||||
TEST_F(M1SamplerTest, NameAndGetDatumNameUnits) {
|
||||
EXPECT_EQ("m1", sampler_->GetName());
|
||||
|
||||
auto datum_name_units = sampler_->GetDatumNameUnits();
|
||||
EXPECT_THAT(datum_name_units,
|
||||
UnorderedElementsAre(std::make_pair("p_cores_temperature", "C"),
|
||||
std::make_pair("e_cores_temperature", "C")));
|
||||
}
|
||||
|
||||
TEST_F(M1SamplerTest, GetSample_AllFieldsAvailable) {
|
||||
power_metrics::M1SensorsReader::TemperaturesCelsius temperatures;
|
||||
temperatures.p_cores = 1;
|
||||
temperatures.e_cores = 2;
|
||||
reader_->set_temperatures(temperatures);
|
||||
|
||||
Sampler::Sample sample = sampler_->GetSample(base::TimeTicks());
|
||||
EXPECT_THAT(sample,
|
||||
UnorderedElementsAre(std::make_pair("p_cores_temperature", 1),
|
||||
std::make_pair("e_cores_temperature", 2)));
|
||||
}
|
||||
|
||||
TEST_F(M1SamplerTest, GetSample_IndividualFieldNotAvailable) {
|
||||
{
|
||||
power_metrics::M1SensorsReader::TemperaturesCelsius temperatures;
|
||||
temperatures.p_cores = absl::nullopt;
|
||||
temperatures.e_cores = 2;
|
||||
reader_->set_temperatures(temperatures);
|
||||
|
||||
Sampler::Sample sample = sampler_->GetSample(base::TimeTicks());
|
||||
EXPECT_THAT(sample,
|
||||
UnorderedElementsAre(std::make_pair("e_cores_temperature", 2)));
|
||||
}
|
||||
|
||||
{
|
||||
power_metrics::M1SensorsReader::TemperaturesCelsius temperatures;
|
||||
temperatures.p_cores = 1;
|
||||
temperatures.e_cores = absl::nullopt;
|
||||
reader_->set_temperatures(temperatures);
|
||||
|
||||
Sampler::Sample sample = sampler_->GetSample(base::TimeTicks());
|
||||
EXPECT_THAT(sample,
|
||||
UnorderedElementsAre(std::make_pair("p_cores_temperature", 1)));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace power_sampler
|
@ -20,6 +20,7 @@
|
||||
#include "tools/mac/power/power_sampler/battery_sampler.h"
|
||||
#include "tools/mac/power/power_sampler/csv_exporter.h"
|
||||
#include "tools/mac/power/power_sampler/json_exporter.h"
|
||||
#include "tools/mac/power/power_sampler/m1_sampler.h"
|
||||
#include "tools/mac/power/power_sampler/main_display_sampler.h"
|
||||
#include "tools/mac/power/power_sampler/resource_coalition_sampler.h"
|
||||
#include "tools/mac/power/power_sampler/sample_counter.h"
|
||||
@ -234,6 +235,13 @@ int main(int argc, char** argv) {
|
||||
return kStatusRuntimeError;
|
||||
}
|
||||
}
|
||||
if (ConsumeSamplerName(power_sampler::M1Sampler::kSamplerName,
|
||||
sampler_names) ||
|
||||
all_samplers) {
|
||||
if (!MaybeAddSamplerToController<power_sampler::M1Sampler>(controller)) {
|
||||
return kStatusRuntimeError;
|
||||
}
|
||||
}
|
||||
if (ConsumeSamplerName(power_sampler::UserIdleLevelSampler::kSamplerName,
|
||||
sampler_names) ||
|
||||
all_samplers) {
|
||||
|
@ -3,6 +3,7 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "tools/mac/power/power_sampler/smc_sampler.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "base/containers/flat_map.h"
|
||||
|
Reference in New Issue
Block a user