1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "PowerCounters.h"
6 #include "nsXULAppAPI.h" // for XRE_IsParentProcess
11 #include <setupapi.h> // for SetupDi*
12 // LogSeverity, defined by setupapi.h to DWORD, messes with other code.
16 #define NTDDI_VERSION NTDDI_WINBLUE
19 using namespace mozilla
;
21 // This is a counter to collect power utilization during profiling.
22 // It cannot be a raw `ProfilerCounter` because we need to manually add/remove
23 // it while the profiler lock is already held.
24 class PowerMeterChannel final
: public BaseProfilerCount
{
26 explicit PowerMeterChannel(const WCHAR
* aChannelName
, ULONGLONG aInitialValue
,
27 ULONGLONG aInitialTime
)
28 : BaseProfilerCount(nullptr, nullptr, nullptr, "power",
30 mChannelName(NS_ConvertUTF16toUTF8(aChannelName
)),
31 mPreviousValue(aInitialValue
),
32 mPreviousTime(aInitialTime
),
34 if (mChannelName
.Equals("RAPL_Package0_PKG")) {
35 mLabel
= "Power: CPU package";
36 mDescription
= mChannelName
.get();
37 } else if (mChannelName
.Equals("RAPL_Package0_PP0")) {
38 mLabel
= "Power: CPU cores";
39 mDescription
= mChannelName
.get();
40 } else if (mChannelName
.Equals("RAPL_Package0_PP1")) {
41 mLabel
= "Power: iGPU";
42 mDescription
= mChannelName
.get();
43 } else if (mChannelName
.Equals("RAPL_Package0_DRAM")) {
44 mLabel
= "Power: DRAM";
45 mDescription
= mChannelName
.get();
48 if (sscanf(mChannelName
.get(), "RAPL_Package0_Core%u_CORE", &coreId
) ==
50 mLabelString
= "Power: CPU core ";
51 mLabelString
.AppendInt(coreId
);
52 mLabel
= mLabelString
.get();
53 mDescription
= mChannelName
.get();
55 mLabel
= mChannelName
.get();
60 CountSample
Sample() override
{
62 result
.count
= mCounter
;
64 result
.isSampleNew
= mIsSampleNew
;
69 void AddSample(ULONGLONG aAbsoluteEnergy
, ULONGLONG aAbsoluteTime
) {
70 // aAbsoluteTime is the time since the system start in 100ns increments.
71 if (aAbsoluteTime
== mPreviousTime
) {
75 if (aAbsoluteEnergy
> mPreviousValue
) {
76 int64_t increment
= aAbsoluteEnergy
- mPreviousValue
;
77 mCounter
+= increment
;
78 mPreviousValue
+= increment
;
79 mPreviousTime
= aAbsoluteTime
;
87 nsCString mChannelName
;
89 // Used as a storage when the label can not be a literal string.
90 nsCString mLabelString
;
92 ULONGLONG mPreviousValue
;
93 ULONGLONG mPreviousTime
;
97 class PowerMeterDevice
{
99 explicit PowerMeterDevice(LPCTSTR aDevicePath
) {
100 mHandle
= ::CreateFile(aDevicePath
, GENERIC_READ
,
101 FILE_SHARE_READ
| FILE_SHARE_WRITE
, nullptr,
102 OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, nullptr);
103 if (mHandle
== INVALID_HANDLE_VALUE
) {
107 EMI_VERSION version
= {0};
110 if (!::DeviceIoControl(mHandle
, IOCTL_EMI_GET_VERSION
, nullptr, 0, &version
,
111 sizeof(version
), &dwOut
, nullptr) ||
112 (version
.EmiVersion
!= EMI_VERSION_V1
&&
113 version
.EmiVersion
!= EMI_VERSION_V2
)) {
117 EMI_METADATA_SIZE size
= {0};
118 if (!::DeviceIoControl(mHandle
, IOCTL_EMI_GET_METADATA_SIZE
, nullptr, 0,
119 &size
, sizeof(size
), &dwOut
, nullptr) ||
120 !size
.MetadataSize
) {
124 UniquePtr
<uint8_t[]> metadata(new (std::nothrow
)
125 uint8_t[size
.MetadataSize
]);
130 if (version
.EmiVersion
== EMI_VERSION_V2
) {
131 EMI_METADATA_V2
* metadata2
=
132 reinterpret_cast<EMI_METADATA_V2
*>(metadata
.get());
133 if (!::DeviceIoControl(mHandle
, IOCTL_EMI_GET_METADATA
, nullptr, 0,
134 metadata2
, size
.MetadataSize
, &dwOut
, nullptr)) {
138 if (!mChannels
.reserve(metadata2
->ChannelCount
)) {
143 MakeUnique
<EMI_CHANNEL_MEASUREMENT_DATA
[]>(metadata2
->ChannelCount
);
148 if (!::DeviceIoControl(
149 mHandle
, IOCTL_EMI_GET_MEASUREMENT
, nullptr, 0, mDataBuffer
.get(),
150 sizeof(EMI_CHANNEL_MEASUREMENT_DATA
[metadata2
->ChannelCount
]),
155 EMI_CHANNEL_V2
* channel
= &metadata2
->Channels
[0];
156 for (int i
= 0; i
< metadata2
->ChannelCount
; ++i
) {
157 EMI_CHANNEL_MEASUREMENT_DATA
* channel_data
= &mDataBuffer
[i
];
158 mChannels
.infallibleAppend(new PowerMeterChannel(
159 channel
->ChannelName
, channel_data
->AbsoluteEnergy
,
160 channel_data
->AbsoluteTime
));
161 channel
= EMI_CHANNEL_V2_NEXT_CHANNEL(channel
);
163 } else if (version
.EmiVersion
== EMI_VERSION_V1
) {
164 EMI_METADATA_V1
* metadata1
=
165 reinterpret_cast<EMI_METADATA_V1
*>(metadata
.get());
166 if (!::DeviceIoControl(mHandle
, IOCTL_EMI_GET_METADATA
, nullptr, 0,
167 metadata1
, size
.MetadataSize
, &dwOut
, nullptr)) {
171 mDataBuffer
= MakeUnique
<EMI_CHANNEL_MEASUREMENT_DATA
[]>(1);
176 if (!::DeviceIoControl(
177 mHandle
, IOCTL_EMI_GET_MEASUREMENT
, nullptr, 0, mDataBuffer
.get(),
178 sizeof(EMI_CHANNEL_MEASUREMENT_DATA
), &dwOut
, nullptr)) {
182 (void)mChannels
.append(new PowerMeterChannel(
183 metadata1
->MeteredHardwareName
, mDataBuffer
[0].AbsoluteEnergy
,
184 mDataBuffer
[0].AbsoluteTime
));
188 ~PowerMeterDevice() {
189 if (mHandle
!= INVALID_HANDLE_VALUE
) {
190 ::CloseHandle(mHandle
);
195 MOZ_ASSERT(HasChannels());
196 MOZ_ASSERT(mDataBuffer
);
199 if (!::DeviceIoControl(
200 mHandle
, IOCTL_EMI_GET_MEASUREMENT
, nullptr, 0, mDataBuffer
.get(),
201 sizeof(EMI_CHANNEL_MEASUREMENT_DATA
[mChannels
.length()]), &dwOut
,
206 for (size_t i
= 0; i
< mChannels
.length(); ++i
) {
207 EMI_CHANNEL_MEASUREMENT_DATA
* channel_data
= &mDataBuffer
[i
];
208 mChannels
[i
]->AddSample(channel_data
->AbsoluteEnergy
,
209 channel_data
->AbsoluteTime
);
213 bool HasChannels() { return mChannels
.length() != 0; }
214 void AppendCountersTo(PowerCounters::CountVector
& aCounters
) {
215 if (aCounters
.reserve(aCounters
.length() + mChannels
.length())) {
216 for (auto& channel
: mChannels
) {
217 aCounters
.infallibleAppend(channel
.get());
223 Vector
<UniquePtr
<PowerMeterChannel
>, 4> mChannels
;
224 HANDLE mHandle
= INVALID_HANDLE_VALUE
;
225 UniquePtr
<EMI_CHANNEL_MEASUREMENT_DATA
[]> mDataBuffer
;
228 PowerCounters::PowerCounters() {
229 class MOZ_STACK_CLASS HDevInfoHolder final
{
231 explicit HDevInfoHolder(HDEVINFO aHandle
) : mHandle(aHandle
) {}
233 ~HDevInfoHolder() { ::SetupDiDestroyDeviceInfoList(mHandle
); }
239 if (!XRE_IsParentProcess()) {
240 // Energy meters are global, so only sample them on the parent.
244 // Energy Metering Device Interface
245 // {45BD8344-7ED6-49cf-A440-C276C933B053}
247 // Using GUID_DEVICE_ENERGY_METER does not compile as the symbol does not
248 // exist before Windows 10.
249 GUID my_GUID_DEVICE_ENERGY_METER
= {
253 {0xa4, 0x40, 0xc2, 0x76, 0xc9, 0x33, 0xb0, 0x53}};
256 ::SetupDiGetClassDevs(&my_GUID_DEVICE_ENERGY_METER
, nullptr, nullptr,
257 DIGCF_PRESENT
| DIGCF_DEVICEINTERFACE
);
258 if (hdev
== INVALID_HANDLE_VALUE
) {
262 HDevInfoHolder
hdevHolder(hdev
);
265 SP_DEVICE_INTERFACE_DATA did
= {0};
266 did
.cbSize
= sizeof(did
);
268 while (::SetupDiEnumDeviceInterfaces(
269 hdev
, nullptr, &my_GUID_DEVICE_ENERGY_METER
, i
++, &did
)) {
270 DWORD bufferSize
= 0;
271 ::SetupDiGetDeviceInterfaceDetail(hdev
, &did
, nullptr, 0, &bufferSize
,
273 if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER
) {
277 UniquePtr
<uint8_t[]> buffer(new (std::nothrow
) uint8_t[bufferSize
]);
282 PSP_DEVICE_INTERFACE_DETAIL_DATA pdidd
=
283 reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA
>(buffer
.get());
284 MOZ_ASSERT(uintptr_t(buffer
.get()) %
285 alignof(PSP_DEVICE_INTERFACE_DETAIL_DATA
) ==
287 pdidd
->cbSize
= sizeof(*pdidd
);
288 if (!::SetupDiGetDeviceInterfaceDetail(hdev
, &did
, pdidd
, bufferSize
,
289 &bufferSize
, nullptr)) {
293 UniquePtr
<PowerMeterDevice
> pmd
=
294 MakeUnique
<PowerMeterDevice
>(pdidd
->DevicePath
);
295 if (!pmd
->HasChannels() ||
296 !mPowerMeterDevices
.emplaceBack(std::move(pmd
))) {
297 NS_WARNING("PowerMeterDevice without measurement channel (or OOM)");
301 for (auto& device
: mPowerMeterDevices
) {
302 device
->AppendCountersTo(mCounters
);
306 PowerCounters::~PowerCounters() { mCounters
.clear(); }
308 void PowerCounters::Sample() {
309 for (auto& device
: mPowerMeterDevices
) {