1 // Copyright 2021 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/power_metrics/energy_impact_mac.h"
7 #include <Foundation/Foundation.h>
8 #import <IOKit/IOKitLib.h>
10 #include "base/apple/foundation_util.h"
11 #include "base/apple/scoped_cftyperef.h"
12 #include "base/mac/scoped_ioobject.h"
13 #include "base/strings/sys_string_conversions.h"
14 #include "base/time/time.h"
15 #include "components/power_metrics/mach_time_mac.h"
16 #include "components/power_metrics/resource_coalition_mac.h"
18 namespace power_metrics {
22 NSDictionary* MaybeGetDictionaryFromPath(const base::FilePath& path) {
23 // The folder where the energy coefficient plist files are stored.
25 dictionaryWithContentsOfURL:base::apple::FilePathToNSURL(path)
29 double GetNamedCoefficientOrZero(NSDictionary* dict, NSString* key) {
30 NSNumber* num = base::apple::ObjCCast<NSNumber>(dict[key]);
31 return num.floatValue;
36 absl::optional<EnergyImpactCoefficients>
37 ReadCoefficientsForCurrentMachineOrDefault() {
38 absl::optional<std::string> board_id = internal::GetBoardIdForThisMachine();
39 if (!board_id.has_value())
42 return internal::ReadCoefficientsForBoardIdOrDefault(
43 base::FilePath(FILE_PATH_LITERAL("/usr/share/pmenergy")),
47 double ComputeEnergyImpactForResourceUsage(
48 const coalition_resource_usage& data_sample,
49 const EnergyImpactCoefficients& coefficients,
50 const mach_timebase_info_data_t& mach_timebase) {
51 // TODO(https://crbug.com/1249536): The below coefficients are not used
52 // for now. Their units are unknown, and in the case of the network-related
53 // coefficients, it's not clear how to sample the data.
55 // coefficients.kdiskio_bytesread;
56 // coefficients.kdiskio_byteswritten;
57 // coefficients.knetwork_recv_bytes;
58 // coefficients.knetwork_recv_packets;
59 // coefficients.knetwork_sent_bytes;
60 // coefficients.knetwork_sent_packets;
62 // The cumulative CPU usage in |data_sample| is in units of ns, and
63 // |cpu_time_equivalent_ns| is computed in CPU ns up to the end of this
64 // function, where it's converted to units of EnergyImpact.
65 double cpu_time_equivalent_ns = 0.0;
67 // The kcpu_wakeups coefficient on disk is in seconds, but our
68 // intermediate result is in ns, so convert to ns on the fly.
69 cpu_time_equivalent_ns += coefficients.kcpu_wakeups *
70 base::Time::kNanosecondsPerSecond *
71 data_sample.platform_idle_wakeups;
73 // Presumably the kgpu_time coefficient has suitable units for the
74 // conversion of GPU time energy to CPU time energy. There is a fairly
75 // wide spread on this constant seen in /usr/share/pmenergy. On
76 // macOS 11.5.2 the spread is from 0 through 5.9.
77 cpu_time_equivalent_ns += coefficients.kgpu_time *
78 MachTimeToNs(data_sample.gpu_time, mach_timebase);
80 cpu_time_equivalent_ns +=
81 coefficients.kqos_background *
82 MachTimeToNs(data_sample.cpu_time_eqos[THREAD_QOS_BACKGROUND],
84 cpu_time_equivalent_ns +=
85 coefficients.kqos_default *
86 MachTimeToNs(data_sample.cpu_time_eqos[THREAD_QOS_DEFAULT],
88 cpu_time_equivalent_ns +=
89 coefficients.kqos_legacy *
90 MachTimeToNs(data_sample.cpu_time_eqos[THREAD_QOS_LEGACY], mach_timebase);
91 cpu_time_equivalent_ns +=
92 coefficients.kqos_user_initiated *
93 MachTimeToNs(data_sample.cpu_time_eqos[THREAD_QOS_USER_INITIATED],
95 cpu_time_equivalent_ns +=
96 coefficients.kqos_user_interactive *
97 MachTimeToNs(data_sample.cpu_time_eqos[THREAD_QOS_USER_INTERACTIVE],
99 cpu_time_equivalent_ns +=
100 coefficients.kqos_utility *
101 MachTimeToNs(data_sample.cpu_time_eqos[THREAD_QOS_UTILITY],
104 // The conversion ratio for CPU time/EnergyImpact is ns/10ms
105 constexpr double kNsToEI = 1E-7;
106 return cpu_time_equivalent_ns * kNsToEI;
111 absl::optional<EnergyImpactCoefficients> ReadCoefficientsFromPath(
112 const base::FilePath& plist_file) {
114 NSDictionary* dict = MaybeGetDictionaryFromPath(plist_file);
116 return absl::nullopt;
119 NSDictionary* energy_constants = dict[@"energy_constants"];
120 if (!energy_constants) {
121 return absl::nullopt;
124 EnergyImpactCoefficients coefficients{};
125 coefficients.kcpu_time =
126 GetNamedCoefficientOrZero(energy_constants, @"kcpu_time");
127 coefficients.kcpu_wakeups =
128 GetNamedCoefficientOrZero(energy_constants, @"kcpu_wakeups");
130 coefficients.kqos_default =
131 GetNamedCoefficientOrZero(energy_constants, @"kqos_default");
132 coefficients.kqos_background =
133 GetNamedCoefficientOrZero(energy_constants, @"kqos_background");
134 coefficients.kqos_utility =
135 GetNamedCoefficientOrZero(energy_constants, @"kqos_utility");
136 coefficients.kqos_legacy =
137 GetNamedCoefficientOrZero(energy_constants, @"kqos_legacy");
138 coefficients.kqos_user_initiated =
139 GetNamedCoefficientOrZero(energy_constants, @"kqos_user_initiated");
140 coefficients.kqos_user_interactive =
141 GetNamedCoefficientOrZero(energy_constants, @"kqos_user_interactive");
143 coefficients.kdiskio_bytesread =
144 GetNamedCoefficientOrZero(energy_constants, @"kdiskio_bytesread");
145 coefficients.kdiskio_byteswritten =
146 GetNamedCoefficientOrZero(energy_constants, @"kdiskio_byteswritten");
148 coefficients.kgpu_time =
149 GetNamedCoefficientOrZero(energy_constants, @"kgpu_time");
151 coefficients.knetwork_recv_bytes =
152 GetNamedCoefficientOrZero(energy_constants, @"knetwork_recv_bytes");
153 coefficients.knetwork_recv_packets =
154 GetNamedCoefficientOrZero(energy_constants, @"knetwork_recv_packets");
155 coefficients.knetwork_sent_bytes =
156 GetNamedCoefficientOrZero(energy_constants, @"knetwork_sent_bytes");
157 coefficients.knetwork_sent_packets =
158 GetNamedCoefficientOrZero(energy_constants, @"knetwork_sent_packets");
164 absl::optional<EnergyImpactCoefficients> ReadCoefficientsForBoardIdOrDefault(
165 const base::FilePath& directory,
166 const std::string& board_id) {
167 auto coefficients = ReadCoefficientsFromPath(
168 directory.Append(board_id).AddExtension(FILE_PATH_LITERAL("plist")));
169 if (coefficients.has_value())
172 return ReadCoefficientsFromPath(
173 directory.Append(FILE_PATH_LITERAL("default.plist")));
176 absl::optional<std::string> GetBoardIdForThisMachine() {
177 base::mac::ScopedIOObject<io_service_t> platform_expert(
178 IOServiceGetMatchingService(kIOMasterPortDefault,
179 IOServiceMatching("IOPlatformExpertDevice")));
180 if (!platform_expert)
181 return absl::nullopt;
183 // This is what libpmenergy is observed to do in order to retrieve the correct
184 // coefficients file for the local computer.
185 base::apple::ScopedCFTypeRef<CFDataRef> board_id_data(
186 base::apple::CFCast<CFDataRef>(IORegistryEntryCreateCFProperty(
187 platform_expert, CFSTR("board-id"), kCFAllocatorDefault, 0)));
190 return absl::nullopt;
192 return reinterpret_cast<const char*>(CFDataGetBytePtr(board_id_data));
195 } // namespace internal
197 } // namespace power_metrics