From 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 03:47:29 +0200 Subject: Adding upstream version 115.8.0esr. Signed-off-by: Daniel Baumann --- tools/profiler/core/PowerCounters-win.cpp | 342 ++++++++++++++++++++++++++++++ 1 file changed, 342 insertions(+) create mode 100644 tools/profiler/core/PowerCounters-win.cpp (limited to 'tools/profiler/core/PowerCounters-win.cpp') diff --git a/tools/profiler/core/PowerCounters-win.cpp b/tools/profiler/core/PowerCounters-win.cpp new file mode 100644 index 0000000000..f1d05389b6 --- /dev/null +++ b/tools/profiler/core/PowerCounters-win.cpp @@ -0,0 +1,342 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PowerCounters.h" +#include "nsXULAppAPI.h" // for XRE_IsParentProcess +#include "nsString.h" + +#include +#include +#include // for SetupDi* +// LogSeverity, defined by setupapi.h to DWORD, messes with other code. +#undef LogSeverity + +#undef NTDDI_VERSION +#define NTDDI_VERSION NTDDI_WINBLUE +#include + +#ifndef NTDDI_WIN10_RS5 +// EMI v2 API exists in SDK 10.0.17763 (Windows 10 1809 / Redstone 5) and later. +// Our build machines are still on SDK 10.0.17134. +// Remove this block when updating the SDK (bug 1774628). +typedef EMI_METADATA EMI_METADATA_V1; +typedef EMI_MEASUREMENT_DATA EMI_CHANNEL_MEASUREMENT_DATA; +# define EMI_VERSION_V2 2 + +typedef struct { + EMI_MEASUREMENT_UNIT MeasurementUnit; + USHORT ChannelNameSize; + WCHAR ChannelName[ANYSIZE_ARRAY]; +} EMI_CHANNEL_V2; + +typedef struct { + WCHAR HardwareOEM[EMI_NAME_MAX]; + WCHAR HardwareModel[EMI_NAME_MAX]; + USHORT HardwareRevision; + USHORT ChannelCount; + EMI_CHANNEL_V2 Channels[ANYSIZE_ARRAY]; +} EMI_METADATA_V2; + +# define EMI_CHANNEL_V2_LENGTH(_ChannelNameSize) \ + (FIELD_OFFSET(EMI_CHANNEL_V2, ChannelName) + (_ChannelNameSize)) + +# define EMI_CHANNEL_V2_NEXT_CHANNEL(_Channel) \ + ((EMI_CHANNEL_V2*)((PUCHAR)(_Channel) + \ + EMI_CHANNEL_V2_LENGTH((_Channel)->ChannelNameSize))) +#endif + +using namespace mozilla; + +// This is a counter to collect power utilization during profiling. +// It cannot be a raw `ProfilerCounter` because we need to manually add/remove +// it while the profiler lock is already held. +class PowerMeterChannel final : public BaseProfilerCount { + public: + explicit PowerMeterChannel(const WCHAR* aChannelName, ULONGLONG aInitialValue, + ULONGLONG aInitialTime) + : BaseProfilerCount(nullptr, nullptr, nullptr, "power", + "Power utilization"), + mChannelName(NS_ConvertUTF16toUTF8(aChannelName)), + mPreviousValue(aInitialValue), + mPreviousTime(aInitialTime), + mIsSampleNew(true) { + if (mChannelName.Equals("RAPL_Package0_PKG")) { + mLabel = "Power: CPU package"; + mDescription = mChannelName.get(); + } else if (mChannelName.Equals("RAPL_Package0_PP0")) { + mLabel = "Power: CPU cores"; + mDescription = mChannelName.get(); + } else if (mChannelName.Equals("RAPL_Package0_PP1")) { + mLabel = "Power: iGPU"; + mDescription = mChannelName.get(); + } else if (mChannelName.Equals("RAPL_Package0_DRAM")) { + mLabel = "Power: DRAM"; + mDescription = mChannelName.get(); + } else { + unsigned int coreId; + if (sscanf(mChannelName.get(), "RAPL_Package0_Core%u_CORE", &coreId) == + 1) { + mLabelString = "Power: CPU core "; + mLabelString.AppendInt(coreId); + mLabel = mLabelString.get(); + mDescription = mChannelName.get(); + } else { + mLabel = mChannelName.get(); + } + } + } + + CountSample Sample() override { + CountSample result; + result.count = mCounter; + result.number = 0; + result.isSampleNew = mIsSampleNew; + mIsSampleNew = false; + return result; + } + + void AddSample(ULONGLONG aAbsoluteEnergy, ULONGLONG aAbsoluteTime) { + // aAbsoluteTime is the time since the system start in 100ns increments. + if (aAbsoluteTime == mPreviousTime) { + return; + } + + if (aAbsoluteEnergy > mPreviousValue) { + int64_t increment = aAbsoluteEnergy - mPreviousValue; + mCounter += increment; + mPreviousValue += increment; + mPreviousTime = aAbsoluteTime; + } + + mIsSampleNew = true; + } + + private: + int64_t mCounter; + nsCString mChannelName; + + // Used as a storage when the label can not be a literal string. + nsCString mLabelString; + + ULONGLONG mPreviousValue; + ULONGLONG mPreviousTime; + bool mIsSampleNew; +}; + +class PowerMeterDevice { + public: + explicit PowerMeterDevice(LPCTSTR aDevicePath) { + mHandle = ::CreateFile(aDevicePath, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (mHandle == INVALID_HANDLE_VALUE) { + return; + } + + EMI_VERSION version = {0}; + DWORD dwOut; + + if (!::DeviceIoControl(mHandle, IOCTL_EMI_GET_VERSION, nullptr, 0, &version, + sizeof(version), &dwOut, nullptr) || + (version.EmiVersion != EMI_VERSION_V1 && + version.EmiVersion != EMI_VERSION_V2)) { + return; + } + + EMI_METADATA_SIZE size = {0}; + if (!::DeviceIoControl(mHandle, IOCTL_EMI_GET_METADATA_SIZE, nullptr, 0, + &size, sizeof(size), &dwOut, nullptr) || + !size.MetadataSize) { + return; + } + + UniquePtr metadata(new (std::nothrow) + uint8_t[size.MetadataSize]); + if (!metadata) { + return; + } + + if (version.EmiVersion == EMI_VERSION_V2) { + EMI_METADATA_V2* metadata2 = + reinterpret_cast(metadata.get()); + if (!::DeviceIoControl(mHandle, IOCTL_EMI_GET_METADATA, nullptr, 0, + metadata2, size.MetadataSize, &dwOut, nullptr)) { + return; + } + + if (!mChannels.reserve(metadata2->ChannelCount)) { + return; + } + + mDataBuffer = + MakeUnique(metadata2->ChannelCount); + if (!mDataBuffer) { + return; + } + + if (!::DeviceIoControl( + mHandle, IOCTL_EMI_GET_MEASUREMENT, nullptr, 0, mDataBuffer.get(), + sizeof(EMI_CHANNEL_MEASUREMENT_DATA[metadata2->ChannelCount]), + &dwOut, nullptr)) { + return; + } + + EMI_CHANNEL_V2* channel = &metadata2->Channels[0]; + for (int i = 0; i < metadata2->ChannelCount; ++i) { + EMI_CHANNEL_MEASUREMENT_DATA* channel_data = &mDataBuffer[i]; + mChannels.infallibleAppend(new PowerMeterChannel( + channel->ChannelName, channel_data->AbsoluteEnergy, + channel_data->AbsoluteTime)); + channel = EMI_CHANNEL_V2_NEXT_CHANNEL(channel); + } + } else if (version.EmiVersion == EMI_VERSION_V1) { + EMI_METADATA_V1* metadata1 = + reinterpret_cast(metadata.get()); + if (!::DeviceIoControl(mHandle, IOCTL_EMI_GET_METADATA, nullptr, 0, + metadata1, size.MetadataSize, &dwOut, nullptr)) { + return; + } + + mDataBuffer = MakeUnique(1); + if (!mDataBuffer) { + return; + } + + if (!::DeviceIoControl( + mHandle, IOCTL_EMI_GET_MEASUREMENT, nullptr, 0, mDataBuffer.get(), + sizeof(EMI_CHANNEL_MEASUREMENT_DATA), &dwOut, nullptr)) { + return; + } + + (void)mChannels.append(new PowerMeterChannel( + metadata1->MeteredHardwareName, mDataBuffer[0].AbsoluteEnergy, + mDataBuffer[0].AbsoluteTime)); + } + } + + ~PowerMeterDevice() { + if (mHandle != INVALID_HANDLE_VALUE) { + ::CloseHandle(mHandle); + } + } + + void Sample() { + MOZ_ASSERT(HasChannels()); + MOZ_ASSERT(mDataBuffer); + + DWORD dwOut; + if (!::DeviceIoControl( + mHandle, IOCTL_EMI_GET_MEASUREMENT, nullptr, 0, mDataBuffer.get(), + sizeof(EMI_CHANNEL_MEASUREMENT_DATA[mChannels.length()]), &dwOut, + nullptr)) { + return; + } + + for (size_t i = 0; i < mChannels.length(); ++i) { + EMI_CHANNEL_MEASUREMENT_DATA* channel_data = &mDataBuffer[i]; + mChannels[i]->AddSample(channel_data->AbsoluteEnergy, + channel_data->AbsoluteTime); + } + } + + bool HasChannels() { return mChannels.length() != 0; } + void AppendCountersTo(PowerCounters::CountVector& aCounters) { + if (aCounters.reserve(aCounters.length() + mChannels.length())) { + for (auto& channel : mChannels) { + aCounters.infallibleAppend(channel.get()); + } + } + } + + private: + Vector, 4> mChannels; + HANDLE mHandle = INVALID_HANDLE_VALUE; + UniquePtr mDataBuffer; +}; + +PowerCounters::PowerCounters() { + class MOZ_STACK_CLASS HDevInfoHolder final { + public: + explicit HDevInfoHolder(HDEVINFO aHandle) : mHandle(aHandle) {} + + ~HDevInfoHolder() { ::SetupDiDestroyDeviceInfoList(mHandle); } + + private: + HDEVINFO mHandle; + }; + + if (!XRE_IsParentProcess()) { + // Energy meters are global, so only sample them on the parent. + return; + } + + // Energy Metering Device Interface + // {45BD8344-7ED6-49cf-A440-C276C933B053} + // + // Using GUID_DEVICE_ENERGY_METER does not compile as the symbol does not + // exist before Windows 10. + GUID my_GUID_DEVICE_ENERGY_METER = { + 0x45bd8344, + 0x7ed6, + 0x49cf, + {0xa4, 0x40, 0xc2, 0x76, 0xc9, 0x33, 0xb0, 0x53}}; + + HDEVINFO hdev = + ::SetupDiGetClassDevs(&my_GUID_DEVICE_ENERGY_METER, nullptr, nullptr, + DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + if (hdev == INVALID_HANDLE_VALUE) { + return; + } + + HDevInfoHolder hdevHolder(hdev); + + DWORD i = 0; + SP_DEVICE_INTERFACE_DATA did = {0}; + did.cbSize = sizeof(did); + + while (::SetupDiEnumDeviceInterfaces( + hdev, nullptr, &my_GUID_DEVICE_ENERGY_METER, i++, &did)) { + DWORD bufferSize = 0; + ::SetupDiGetDeviceInterfaceDetail(hdev, &did, nullptr, 0, &bufferSize, + nullptr); + if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + continue; + } + + UniquePtr buffer(new (std::nothrow) uint8_t[bufferSize]); + if (!buffer) { + continue; + } + + PSP_DEVICE_INTERFACE_DETAIL_DATA pdidd = + reinterpret_cast(buffer.get()); + MOZ_ASSERT(uintptr_t(buffer.get()) % + alignof(PSP_DEVICE_INTERFACE_DETAIL_DATA) == + 0); + pdidd->cbSize = sizeof(*pdidd); + if (!::SetupDiGetDeviceInterfaceDetail(hdev, &did, pdidd, bufferSize, + &bufferSize, nullptr)) { + continue; + } + + UniquePtr pmd = + MakeUnique(pdidd->DevicePath); + if (!pmd->HasChannels() || + !mPowerMeterDevices.emplaceBack(std::move(pmd))) { + NS_WARNING("PowerMeterDevice without measurement channel (or OOM)"); + } + } + + for (auto& device : mPowerMeterDevices) { + device->AppendCountersTo(mCounters); + } +} + +PowerCounters::~PowerCounters() { mCounters.clear(); } + +void PowerCounters::Sample() { + for (auto& device : mPowerMeterDevices) { + device->Sample(); + } +} -- cgit v1.2.3