Bug 1774844 - record EMI data as profiler counters, r=gerald.
authorFlorian Queze <florian@queze.net>
Wed, 29 Jun 2022 21:24:12 +0000
changeset 622609 185ab81e5f51a52139387f5f536100d423d59a2a
parent 622608 4c9f1546521177b1fd2592597908bc7be9377e63
child 622610 0839b2f7ae02263470ed9edd63ea415ad872557c
push id39918
push userbszekely@mozilla.com
push dateThu, 30 Jun 2022 09:55:19 +0000
treeherdermozilla-central@65e579f52525 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgerald
bugs1774844
milestone104.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1774844 - record EMI data as profiler counters, r=gerald. Differential Revision: https://phabricator.services.mozilla.com/D149653
tools/profiler/core/PowerCounters-win.cpp
tools/profiler/core/PowerCounters.h
tools/profiler/core/platform.cpp
tools/profiler/moz.build
tools/profiler/public/ProfilerCounts.h
new file mode 100644
--- /dev/null
+++ b/tools/profiler/core/PowerCounters-win.cpp
@@ -0,0 +1,315 @@
+/* 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 <windows.h>
+#include <devioctl.h>
+#include <setupapi.h>  // for SetupDi*
+// LogSeverity, defined by setupapi.h to DWORD, messes with other code.
+#undef LogSeverity
+
+#undef NTDDI_VERSION
+#define NTDDI_VERSION NTDDI_WINBLUE
+#include <emi.h>
+
+#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) {
+    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;
+  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<uint8_t[]> metadata(new (std::nothrow)
+                                      uint8_t[size.MetadataSize]);
+    if (!metadata) {
+      return;
+    }
+
+    if (version.EmiVersion == EMI_VERSION_V2) {
+      EMI_METADATA_V2* metadata2 =
+          reinterpret_cast<EMI_METADATA_V2*>(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<EMI_CHANNEL_MEASUREMENT_DATA[]>(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<EMI_METADATA_V1*>(metadata.get());
+      if (!::DeviceIoControl(mHandle, IOCTL_EMI_GET_METADATA, nullptr, 0,
+                             metadata1, size.MetadataSize, &dwOut, nullptr)) {
+        return;
+      }
+
+      mDataBuffer = MakeUnique<EMI_CHANNEL_MEASUREMENT_DATA[]>(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<UniquePtr<PowerMeterChannel>, 4> mChannels;
+  HANDLE mHandle = INVALID_HANDLE_VALUE;
+  UniquePtr<EMI_CHANNEL_MEASUREMENT_DATA[]> 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<uint8_t[]> buffer(new (std::nothrow) uint8_t[bufferSize]);
+    if (!buffer) {
+      continue;
+    }
+
+    PSP_DEVICE_INTERFACE_DETAIL_DATA pdidd =
+        reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA>(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<PowerMeterDevice> pmd =
+        MakeUnique<PowerMeterDevice>(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();
+  }
+}
new file mode 100644
--- /dev/null
+++ b/tools/profiler/core/PowerCounters.h
@@ -0,0 +1,40 @@
+/* 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/. */
+
+#ifndef TOOLS_POWERCOUNTERS_H_
+#define TOOLS_POWERCOUNTERS_H_
+
+#include "PlatformMacros.h"
+#include "mozilla/ProfilerCounts.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Vector.h"
+
+#if defined(_MSC_VER)
+class PowerMeterDevice;
+#endif
+
+class PowerCounters {
+ public:
+#if defined(_MSC_VER)
+  explicit PowerCounters();
+  ~PowerCounters();
+  void Sample();
+#else
+  explicit PowerCounters(){};
+  ~PowerCounters(){};
+  void Sample(){};
+#endif
+
+  using CountVector = mozilla::Vector<BaseProfilerCount*, 4>;
+  const CountVector& GetCounters() { return mCounters; }
+
+ private:
+  CountVector mCounters;
+
+#if defined(_MSC_VER)
+  mozilla::Vector<mozilla::UniquePtr<PowerMeterDevice>> mPowerMeterDevices;
+#endif
+};
+
+#endif /* ndef TOOLS_POWERCOUNTERS_H_ */
--- a/tools/profiler/core/platform.cpp
+++ b/tools/profiler/core/platform.cpp
@@ -26,16 +26,17 @@
 //   ProfileBuffer. The sampling is done from off-thread, and so uses
 //   SuspendAndSampleAndResumeThread() to get the register values.
 
 #include "platform.h"
 
 #include "GeckoProfiler.h"
 #include "GeckoProfilerReporter.h"
 #include "PageInformation.h"
+#include "PowerCounters.h"
 #include "ProfileBuffer.h"
 #include "ProfiledThreadData.h"
 #include "ProfilerBacktrace.h"
 #include "ProfilerChild.h"
 #include "ProfilerCodeAddressService.h"
 #include "ProfilerControl.h"
 #include "ProfilerIOInterposeObserver.h"
 #include "ProfilerParent.h"
@@ -762,16 +763,17 @@ class ActivePS {
         mProfileBuffer([this]() -> ProfileChunkedBuffer& {
           ProfileChunkedBuffer& coreBuffer = profiler_get_core_buffer();
           coreBuffer.SetChunkManagerIfDifferent(*mProfileBufferChunkManager);
           return coreBuffer;
         }()),
         mMaybeProcessCPUCounter(ProfilerFeature::HasProcessCPU(aFeatures)
                                     ? new ProcessCPUCounter(aLock)
                                     : nullptr),
+        mMaybePowerCounters(nullptr),
         // The new sampler thread doesn't start sampling immediately because the
         // main loop within Run() is blocked until this function's caller
         // unlocks gPSMutex.
         mSamplerThread(
             NewSamplerThread(aLock, mGeneration, aInterval, aFeatures)),
         mIsPaused(false),
         mIsSamplingPaused(false) {
     // Deep copy and lower-case aFilters.
@@ -807,22 +809,33 @@ class ActivePS {
               IOInterposer::Init();
               IOInterposer::Register(
                   IOInterposeObserver::OpAll,
                   &ProfilerIOInterposeObserver::GetInstance());
             }));
       }
     }
 #endif
+
+    if (ProfilerFeature::HasPower(aFeatures)) {
+      mMaybePowerCounters = new PowerCounters();
+      for (const auto& powerCounter : mMaybePowerCounters->GetCounters()) {
+        locked_profiler_add_sampled_counter(aLock, powerCounter);
+      }
+    }
   }
 
   ~ActivePS() {
     MOZ_ASSERT(
         !mMaybeProcessCPUCounter,
         "mMaybeProcessCPUCounter should have been deleted before ~ActivePS()");
+    MOZ_ASSERT(
+        !mMaybePowerCounters,
+        "mMaybePowerCounters should have been deleted before ~ActivePS()");
+
 #if !defined(RELEASE_OR_BETA)
     if (ShouldInterposeIOs()) {
       // We need to unregister the observer on the main thread, because that's
       // where we've registered it.
       if (NS_IsMainThread()) {
         IOInterposer::Unregister(IOInterposeObserver::OpAll,
                                  &ProfilerIOInterposeObserver::GetInstance());
       } else {
@@ -883,16 +896,26 @@ class ActivePS {
   [[nodiscard]] static SamplerThread* Destroy(PSLockRef aLock) {
     MOZ_ASSERT(sInstance);
     if (sInstance->mMaybeProcessCPUCounter) {
       locked_profiler_remove_sampled_counter(
           aLock, sInstance->mMaybeProcessCPUCounter);
       delete sInstance->mMaybeProcessCPUCounter;
       sInstance->mMaybeProcessCPUCounter = nullptr;
     }
+
+    if (sInstance->mMaybePowerCounters) {
+      for (const auto& powerCounter :
+           sInstance->mMaybePowerCounters->GetCounters()) {
+        locked_profiler_remove_sampled_counter(aLock, powerCounter);
+      }
+      delete sInstance->mMaybePowerCounters;
+      sInstance->mMaybePowerCounters = nullptr;
+    }
+
     auto samplerThread = sInstance->mSamplerThread;
     delete sInstance;
     sInstance = nullptr;
 
     return samplerThread;
   }
 
   static bool Exists(PSLockRef) { return !!sInstance; }
@@ -1187,16 +1210,18 @@ class ActivePS {
 
     void Add(int64_t aNumber) { mCounter += aNumber; }
 
    private:
     ProfilerAtomicSigned mCounter;
   };
   PS_GET(ProcessCPUCounter*, MaybeProcessCPUCounter);
 
+  PS_GET(PowerCounters*, MaybePowerCounters);
+
   PS_GET_AND_SET(bool, IsPaused)
 
   // True if sampling is paused (though generic `SetIsPaused()` or specific
   // `SetIsSamplingPaused()`).
   static bool IsSamplingPaused(PSLockRef lock) {
     MOZ_ASSERT(sInstance);
     return IsPaused(lock) || sInstance->mIsSamplingPaused;
   }
@@ -1414,16 +1439,19 @@ class ActivePS {
   // Registered pages are being moved to this array after unregistration.
   // We are keeping them in case we need them in the profile data.
   // We are removing them when we ensure that we won't need them anymore.
   Vector<RefPtr<PageInformation>> mDeadProfiledPages;
 
   // Used to collect process CPU utilization values, if the feature is on.
   ProcessCPUCounter* mMaybeProcessCPUCounter;
 
+  // Used to collect power use data, if the power feature is on.
+  PowerCounters* mMaybePowerCounters;
+
   // The current sampler thread. This class is not responsible for destroying
   // the SamplerThread object; the Destroy() method returns it so the caller
   // can destroy it.
   SamplerThread* const mSamplerThread;
 
   // Is the profiler fully paused?
   bool mIsPaused;
 
@@ -3914,43 +3942,46 @@ void SamplerThread::Run() {
           RunningTimes processRunningTimesDiff =
               GetProcessRunningTimesDiff(lock, processRunningTimes);
           Maybe<uint64_t> cpu = processRunningTimesDiff.GetJsonThreadCPUDelta();
           if (cpu) {
             processCPUCounter->Add(static_cast<int64_t>(*cpu));
           }
         }
 
+        if (PowerCounters* powerCounters = ActivePS::MaybePowerCounters(lock);
+            powerCounters) {
+          powerCounters->Sample();
+        }
+
         // handle per-process generic counters
         const Vector<BaseProfilerCount*>& counters = CorePS::Counters(lock);
         for (auto& counter : counters) {
-          // create Buffer entries for each counter
-          buffer.AddEntry(ProfileBufferEntry::CounterId(counter));
-          buffer.AddEntry(ProfileBufferEntry::Time(sampleStartDeltaMs));
-          // XXX support keyed maps of counts
-          // In the future, we'll support keyed counters - for example, counters
-          // with a key which is a thread ID. For "simple" counters we'll just
-          // use a key of 0.
-          int64_t count;
-          uint64_t number;
-          counter->Sample(count, number);
+          if (auto sample = counter->Sample(); sample.isSampleNew) {
+            // create Buffer entries for each counter
+            buffer.AddEntry(ProfileBufferEntry::CounterId(counter));
+            buffer.AddEntry(ProfileBufferEntry::Time(sampleStartDeltaMs));
 #if defined(MOZ_REPLACE_MALLOC) && defined(MOZ_PROFILER_MEMORY)
-          if (ActivePS::IsMemoryCounter(counter)) {
-            // For the memory counter, substract the size of our buffer to avoid
-            // giving the misleading impression that the memory use keeps on
-            // growing when it's just the profiler session that's using a larger
-            // buffer as it gets longer.
-            count -= static_cast<int64_t>(
-                ActivePS::ControlledChunkManager(lock).TotalSize());
-          }
+            if (ActivePS::IsMemoryCounter(counter)) {
+              // For the memory counter, substract the size of our buffer to
+              // avoid giving the misleading impression that the memory use
+              // keeps on growing when it's just the profiler session that's
+              // using a larger buffer as it gets longer.
+              sample.count -= static_cast<int64_t>(
+                  ActivePS::ControlledChunkManager(lock).TotalSize());
+            }
 #endif
-          buffer.AddEntry(ProfileBufferEntry::CounterKey(0));
-          buffer.AddEntry(ProfileBufferEntry::Count(count));
-          if (number) {
-            buffer.AddEntry(ProfileBufferEntry::Number(number));
+            // In the future, we may support keyed counters - for example,
+            // counters with a key which is a thread ID. For "simple" counters
+            // we'll just use a key of 0.
+            buffer.AddEntry(ProfileBufferEntry::CounterKey(0));
+            buffer.AddEntry(ProfileBufferEntry::Count(sample.count));
+            if (sample.number) {
+              buffer.AddEntry(ProfileBufferEntry::Number(sample.number));
+            }
           }
         }
         TimeStamp countersSampled = TimeStamp::Now();
 
         if (stackSampling || cpuUtilization) {
           samplingState = SamplingState::SamplingCompleted;
 
           // Prevent threads from ending (or starting) and allow access to all
--- a/tools/profiler/moz.build
+++ b/tools/profiler/moz.build
@@ -82,16 +82,20 @@ if CONFIG["MOZ_GECKO_PROFILER"]:
             SOURCES += [
                 "core/EHABIStackWalk.cpp",
             ]
     elif CONFIG["OS_TARGET"] == "Darwin":
         UNIFIED_SOURCES += [
             "core/shared-libraries-macos.cc",
         ]
     elif CONFIG["OS_TARGET"] == "WINNT":
+        if CONFIG["CC_TYPE"] == "clang-cl":
+            UNIFIED_SOURCES += [
+                "core/PowerCounters-win.cpp",
+            ]
         SOURCES += [
             "core/shared-libraries-win32.cc",
         ]
 
     LOCAL_INCLUDES += [
         "/caps",
         "/docshell/base",
         "/ipc/chromium/src",
--- a/tools/profiler/public/ProfilerCounts.h
+++ b/tools/profiler/public/ProfilerCounts.h
@@ -94,25 +94,39 @@ class BaseProfilerCount {
   }
 
   virtual ~BaseProfilerCount() {
 #  ifdef DEBUG
     mCanary = 0;
 #  endif
   }
 
-  virtual void Sample(int64_t& aCounter, uint64_t& aNumber) {
+  struct CountSample {
+    int64_t count;
+    uint64_t number;
+    // This field indicates if the sample has already been consummed by a call
+    // to the Sample() method. This allows the profiler to discard duplicate
+    // samples if the counter sampling rate is lower than the profiler sampling
+    // rate. This can happen for example with some power meters that sample up
+    // to every 10ms.
+    // It should always be true when calling Sample() for the first time.
+    bool isSampleNew;
+  };
+  virtual CountSample Sample() {
     MOZ_ASSERT(mCanary == COUNTER_CANARY);
 
-    aCounter = *mCounter;
-    aNumber = mNumber ? *mNumber : 0;
+    CountSample result;
+    result.count = *mCounter;
+    result.number = mNumber ? *mNumber : 0;
 #  ifdef DEBUG
-    MOZ_ASSERT(aNumber >= mPrevNumber);
-    mPrevNumber = aNumber;
+    MOZ_ASSERT(result.number >= mPrevNumber);
+    mPrevNumber = result.number;
 #  endif
+    result.isSampleNew = true;
+    return result;
   }
 
   void Clear() {
     *mCounter = 0;
     // We don't reset *mNumber or mPrevNumber.  We encode numbers as
     // positive deltas, and currently we only care about the deltas (for
     // e.g. heatmaps).  If we ever need to clear mNumber as well, we can an
     // alternative method (Reset()) to do so.