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.
17 using namespace mozilla
;
19 // This is a counter to collect power utilization during profiling.
20 // It cannot be a raw `ProfilerCounter` because we need to manually add/remove
21 // it while the profiler lock is already held.
22 class PowerMeterChannel final
: public BaseProfilerCount
{
24 explicit PowerMeterChannel(const WCHAR
* aChannelName
, ULONGLONG aInitialValue
,
25 ULONGLONG aInitialTime
)
26 : BaseProfilerCount(nullptr, nullptr, nullptr, "power",
28 mChannelName(NS_ConvertUTF16toUTF8(aChannelName
)),
29 mPreviousValue(aInitialValue
),
30 mPreviousTime(aInitialTime
),
32 if (mChannelName
.Equals("RAPL_Package0_PKG")) {
33 mLabel
= "Power: CPU package";
34 mDescription
= mChannelName
.get();
35 } else if (mChannelName
.Equals("RAPL_Package0_PP0")) {
36 mLabel
= "Power: CPU cores";
37 mDescription
= mChannelName
.get();
38 } else if (mChannelName
.Equals("RAPL_Package0_PP1")) {
39 mLabel
= "Power: iGPU";
40 mDescription
= mChannelName
.get();
41 } else if (mChannelName
.Equals("RAPL_Package0_DRAM")) {
42 mLabel
= "Power: DRAM";
43 mDescription
= mChannelName
.get();
46 if (sscanf(mChannelName
.get(), "RAPL_Package0_Core%u_CORE", &coreId
) ==
48 mLabelString
= "Power: CPU core ";
49 mLabelString
.AppendInt(coreId
);
50 mLabel
= mLabelString
.get();
51 mDescription
= mChannelName
.get();
53 mLabel
= mChannelName
.get();
58 CountSample
Sample() override
{
60 result
.count
= mCounter
;
62 result
.isSampleNew
= mIsSampleNew
;
67 void AddSample(ULONGLONG aAbsoluteEnergy
, ULONGLONG aAbsoluteTime
) {
68 // aAbsoluteTime is the time since the system start in 100ns increments.
69 if (aAbsoluteTime
== mPreviousTime
) {
73 if (aAbsoluteEnergy
> mPreviousValue
) {
74 int64_t increment
= aAbsoluteEnergy
- mPreviousValue
;
75 mCounter
+= increment
;
76 mPreviousValue
+= increment
;
77 mPreviousTime
= aAbsoluteTime
;
85 nsCString mChannelName
;
87 // Used as a storage when the label can not be a literal string.
88 nsCString mLabelString
;
90 ULONGLONG mPreviousValue
;
91 ULONGLONG mPreviousTime
;
95 class PowerMeterDevice
{
97 explicit PowerMeterDevice(LPCTSTR aDevicePath
) {
98 mHandle
= ::CreateFile(aDevicePath
, GENERIC_READ
,
99 FILE_SHARE_READ
| FILE_SHARE_WRITE
, nullptr,
100 OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, nullptr);
101 if (mHandle
== INVALID_HANDLE_VALUE
) {
105 EMI_VERSION version
= {0};
108 if (!::DeviceIoControl(mHandle
, IOCTL_EMI_GET_VERSION
, nullptr, 0, &version
,
109 sizeof(version
), &dwOut
, nullptr) ||
110 (version
.EmiVersion
!= EMI_VERSION_V1
&&
111 version
.EmiVersion
!= EMI_VERSION_V2
)) {
115 EMI_METADATA_SIZE size
= {0};
116 if (!::DeviceIoControl(mHandle
, IOCTL_EMI_GET_METADATA_SIZE
, nullptr, 0,
117 &size
, sizeof(size
), &dwOut
, nullptr) ||
118 !size
.MetadataSize
) {
122 UniquePtr
<uint8_t[]> metadata(new (std::nothrow
)
123 uint8_t[size
.MetadataSize
]);
128 if (version
.EmiVersion
== EMI_VERSION_V2
) {
129 EMI_METADATA_V2
* metadata2
=
130 reinterpret_cast<EMI_METADATA_V2
*>(metadata
.get());
131 if (!::DeviceIoControl(mHandle
, IOCTL_EMI_GET_METADATA
, nullptr, 0,
132 metadata2
, size
.MetadataSize
, &dwOut
, nullptr)) {
136 if (!mChannels
.reserve(metadata2
->ChannelCount
)) {
141 MakeUnique
<EMI_CHANNEL_MEASUREMENT_DATA
[]>(metadata2
->ChannelCount
);
146 if (!::DeviceIoControl(
147 mHandle
, IOCTL_EMI_GET_MEASUREMENT
, nullptr, 0, mDataBuffer
.get(),
148 sizeof(EMI_CHANNEL_MEASUREMENT_DATA
[metadata2
->ChannelCount
]),
153 EMI_CHANNEL_V2
* channel
= &metadata2
->Channels
[0];
154 for (int i
= 0; i
< metadata2
->ChannelCount
; ++i
) {
155 EMI_CHANNEL_MEASUREMENT_DATA
* channel_data
= &mDataBuffer
[i
];
156 mChannels
.infallibleAppend(new PowerMeterChannel(
157 channel
->ChannelName
, channel_data
->AbsoluteEnergy
,
158 channel_data
->AbsoluteTime
));
159 channel
= EMI_CHANNEL_V2_NEXT_CHANNEL(channel
);
161 } else if (version
.EmiVersion
== EMI_VERSION_V1
) {
162 EMI_METADATA_V1
* metadata1
=
163 reinterpret_cast<EMI_METADATA_V1
*>(metadata
.get());
164 if (!::DeviceIoControl(mHandle
, IOCTL_EMI_GET_METADATA
, nullptr, 0,
165 metadata1
, size
.MetadataSize
, &dwOut
, nullptr)) {
169 mDataBuffer
= MakeUnique
<EMI_CHANNEL_MEASUREMENT_DATA
[]>(1);
174 if (!::DeviceIoControl(
175 mHandle
, IOCTL_EMI_GET_MEASUREMENT
, nullptr, 0, mDataBuffer
.get(),
176 sizeof(EMI_CHANNEL_MEASUREMENT_DATA
), &dwOut
, nullptr)) {
180 (void)mChannels
.append(new PowerMeterChannel(
181 metadata1
->MeteredHardwareName
, mDataBuffer
[0].AbsoluteEnergy
,
182 mDataBuffer
[0].AbsoluteTime
));
186 ~PowerMeterDevice() {
187 if (mHandle
!= INVALID_HANDLE_VALUE
) {
188 ::CloseHandle(mHandle
);
193 MOZ_ASSERT(HasChannels());
194 MOZ_ASSERT(mDataBuffer
);
197 if (!::DeviceIoControl(
198 mHandle
, IOCTL_EMI_GET_MEASUREMENT
, nullptr, 0, mDataBuffer
.get(),
199 sizeof(EMI_CHANNEL_MEASUREMENT_DATA
[mChannels
.length()]), &dwOut
,
204 for (size_t i
= 0; i
< mChannels
.length(); ++i
) {
205 EMI_CHANNEL_MEASUREMENT_DATA
* channel_data
= &mDataBuffer
[i
];
206 mChannels
[i
]->AddSample(channel_data
->AbsoluteEnergy
,
207 channel_data
->AbsoluteTime
);
211 bool HasChannels() { return mChannels
.length() != 0; }
212 void AppendCountersTo(PowerCounters::CountVector
& aCounters
) {
213 if (aCounters
.reserve(aCounters
.length() + mChannels
.length())) {
214 for (auto& channel
: mChannels
) {
215 aCounters
.infallibleAppend(channel
.get());
221 Vector
<UniquePtr
<PowerMeterChannel
>, 4> mChannels
;
222 HANDLE mHandle
= INVALID_HANDLE_VALUE
;
223 UniquePtr
<EMI_CHANNEL_MEASUREMENT_DATA
[]> mDataBuffer
;
226 PowerCounters::PowerCounters() {
227 class MOZ_STACK_CLASS HDevInfoHolder final
{
229 explicit HDevInfoHolder(HDEVINFO aHandle
) : mHandle(aHandle
) {}
231 ~HDevInfoHolder() { ::SetupDiDestroyDeviceInfoList(mHandle
); }
237 if (!XRE_IsParentProcess()) {
238 // Energy meters are global, so only sample them on the parent.
242 // Energy Metering Device Interface
243 // {45BD8344-7ED6-49cf-A440-C276C933B053}
245 // Using GUID_DEVICE_ENERGY_METER does not compile as the symbol does not
246 // exist before Windows 10.
247 GUID my_GUID_DEVICE_ENERGY_METER
= {
251 {0xa4, 0x40, 0xc2, 0x76, 0xc9, 0x33, 0xb0, 0x53}};
254 ::SetupDiGetClassDevs(&my_GUID_DEVICE_ENERGY_METER
, nullptr, nullptr,
255 DIGCF_PRESENT
| DIGCF_DEVICEINTERFACE
);
256 if (hdev
== INVALID_HANDLE_VALUE
) {
260 HDevInfoHolder
hdevHolder(hdev
);
263 SP_DEVICE_INTERFACE_DATA did
= {0};
264 did
.cbSize
= sizeof(did
);
266 while (::SetupDiEnumDeviceInterfaces(
267 hdev
, nullptr, &my_GUID_DEVICE_ENERGY_METER
, i
++, &did
)) {
268 DWORD bufferSize
= 0;
269 ::SetupDiGetDeviceInterfaceDetail(hdev
, &did
, nullptr, 0, &bufferSize
,
271 if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER
) {
275 UniquePtr
<uint8_t[]> buffer(new (std::nothrow
) uint8_t[bufferSize
]);
280 PSP_DEVICE_INTERFACE_DETAIL_DATA pdidd
=
281 reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA
>(buffer
.get());
282 MOZ_ASSERT(uintptr_t(buffer
.get()) %
283 alignof(PSP_DEVICE_INTERFACE_DETAIL_DATA
) ==
285 pdidd
->cbSize
= sizeof(*pdidd
);
286 if (!::SetupDiGetDeviceInterfaceDetail(hdev
, &did
, pdidd
, bufferSize
,
287 &bufferSize
, nullptr)) {
291 UniquePtr
<PowerMeterDevice
> pmd
=
292 MakeUnique
<PowerMeterDevice
>(pdidd
->DevicePath
);
293 if (!pmd
->HasChannels() ||
294 !mPowerMeterDevices
.emplaceBack(std::move(pmd
))) {
295 NS_WARNING("PowerMeterDevice without measurement channel (or OOM)");
299 for (auto& device
: mPowerMeterDevices
) {
300 device
->AppendCountersTo(mCounters
);
304 PowerCounters::~PowerCounters() { mCounters
.clear(); }
306 void PowerCounters::Sample() {
307 for (auto& device
: mPowerMeterDevices
) {