Skip to content

Commit

Permalink
Add power, frequency, and temperature monitoring with Intel Power Gad…
Browse files Browse the repository at this point in the history
…get.

Based on sample code at:
https://software.intel.com/en-us/blogs/2012/12/13/using-the-intel-power-gadget-api-on-mac-os-x
I got the power status class to look for and use Intel Power Gadget if it
is installed. This allows emittin events for the CPU power draw, temperature,
and frequency. The temperature and frequency are sampled at that point but the
power draw represents power consumption since the last sample and is, apparently, quite
accurate.

The documentation says that the temperature should be in Celsius but on some machines
I get results that are clearly Fahrenheit.

This should help with investigations of thermal throttling.
  • Loading branch information
randomascii committed Jun 6, 2015
1 parent 8d964b9 commit 556a82e
Show file tree
Hide file tree
Showing 10 changed files with 156 additions and 1 deletion.
15 changes: 15 additions & 0 deletions ETWProviders/etwprof.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,21 @@ void ETWMarkBatteryStatus(_In_z_ PCSTR powerState, float batteryPercentage, _In_
EventWriteMarkBatteryStatus(powerState, batteryPercentage, rate);
}

PLATFORM_INTERFACE void ETWMarkCPUFrequency(_In_z_ PCWSTR MSRName, double frequencyMHz)
{
EventWriteMarkCPUFrequency(MSRName, frequencyMHz);
}

PLATFORM_INTERFACE void ETWMarkCPUPower(_In_z_ PCWSTR MSRName, double powerW, double energymWh)
{
EventWriteMarkCPUPower(MSRName, powerW, energymWh);
}

PLATFORM_INTERFACE void ETWMarkCPUTemp(_In_z_ PCWSTR MSRName, double tempC, double maxTempC)
{
EventWriteMarkCPUTemp(MSRName, tempC, maxTempC);
}

// Track the depth of ETW Begin/End pairs. This needs to be per-thread
// if we start emitting marks on multiple threads. Using __declspec(thread)
// has some problems on Windows XP, but since these ETW functions only work
Expand Down
22 changes: 21 additions & 1 deletion ETWProviders/etwproviders.man
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,20 @@ Adjust that to match your games actual install path.
<data inType="win:Float" name="Battery percentage" />
<data inType="win:AnsiString" name="Rate" />
</template>
<template tid="T_Frequency">
<data inType="win:UnicodeString" name="MSR name" />
<data inType="win:Double" name="Frequency (MHz)"/>
</template>
<template tid="T_Power">
<data inType="win:UnicodeString" name="MSR name" />
<data inType="win:Double" name="Power (W)"/>
<data inType="win:Double" name="Energy (mWh)"/>
</template>
<template tid="T_Temp">
<data inType="win:UnicodeString" name="MSR name" />
<data inType="win:Double" name="Temp (C)"/>
<data inType="win:Double" name="Max temp (C)"/>
</template>
</templates>
<keywords>
<keyword name="HighFrequency" mask="0x2" />
Expand All @@ -131,7 +145,10 @@ Adjust that to match your games actual install path.
<task name="Block" symbol="Block_Task" value="1" eventGUID="{4E9A75EB-4FBA-4BA0-9A1B-2175B671A16D}"/>
<task name="ThreadID" symbol="ThreadID_Task" value="2" eventGUID="{F2EC684F-AD3A-4AF5-8B10-E7B29AF67EE2}"/>
<task name="WorkingSet" symbol="WorkingSet_Task" value="3" eventGUID="{68438942-F3E7-47D0-BA01-30F0304F9D2D}"/>
<task name="BatteryStatus" symbol="BatterStatus_Task" value="4" eventGUID="{F54A4B5D-C295-42DD-A79A-F2D9C5EFCBD2}"/>
<task name="BatteryStatus" symbol="BatteryStatus_Task" value="4" eventGUID="{F54A4B5D-C295-42DD-A79A-F2D9C5EFCBD2}"/>
<task name="FrequencyStatus" symbol="FrequencyStatus_Task" value="5" eventGUID="{0A875F88-5FFE-4BC7-9B46-E4FA87754610}"/>
<task name="PowerStatus" symbol="PowerStatus_Task" value="6" eventGUID="{484C71EE-2A86-466C-88AB-EB7DA15D6E86}"/>
<task name="TempStatus" symbol="TempStatus_Task" value="7" eventGUID="{D94A8004-28EF-467E-98E2-12B00E3DC1EE}"/>
</tasks>
<events>
<event symbol="Start" template="T_Start" value="100" task="Block" opcode="Begin" keywords="NormalFrequency" />
Expand All @@ -144,6 +161,9 @@ Adjust that to match your games actual install path.
<event symbol="Mark2F" template="T_Mark2F" value="107" task="Block" opcode="Mark" keywords="NormalFrequency" />
<event symbol="MarkWorkingSet" template="T_WorkingSet" value="108" task="WorkingSet" opcode="Information" keywords="NormalFrequency" />
<event symbol="MarkBatteryStatus" template="T_Battery" value="109" task="BatteryStatus" opcode="Information" keywords="NormalFrequency" />
<event symbol="MarkCPUFrequency" template="T_Frequency" value="110" task="FrequencyStatus" opcode="Information" keywords="NormalFrequency" />
<event symbol="MarkCPUPower" template="T_Power" value="111" task="PowerStatus" opcode="Information" keywords="NormalFrequency" />
<event symbol="MarkCPUTemp" template="T_Temp" value="112" task="TempStatus" opcode="Information" keywords="NormalFrequency" />
</events>
</provider>

Expand Down
93 changes: 93 additions & 0 deletions UIforETW/PowerStatus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ limitations under the License.

#include "stdafx.h"

#include <string>
#include <stdlib.h>

#include "PowerStatus.h"
#include <ETWProviders\etwprof.h>

Expand All @@ -29,6 +32,67 @@ limitations under the License.

const int kSamplingInterval = 200;

// These correspond to the funcID values returned by GetMsrFunc
const int MSR_FUNC_FREQ = 0;
const int MSR_FUNC_POWER = 1;
const int MSR_FUNC_TEMP = 2;
const int MSR_FUNC_MAX_POWER = 3; /* ????? */

void CPowerStatusMonitor::SampleCPUPowerState()
{
if (!IntelEnergyLibInitialize || !GetNumMsrs || !GetMsrName || !GetMsrFunc ||
!GetPowerData || !ReadSample)
{
return;
}

int numMSRs = 0;
GetNumMsrs(&numMSRs);
ReadSample();
for (int i = 0; i < numMSRs; ++i)
{
int funcID;
wchar_t MSRName[1024];
GetMsrFunc(i, &funcID);
GetMsrName(i, MSRName);

int nData;
double data[3] = {};
GetPowerData(0, i, data, &nData);

if (funcID == MSR_FUNC_FREQ)
{
ETWMarkCPUFrequency(MSRName, (float)data[0]);
//outputPrintf(L"%s = %4.0f MHz\n", MSRName, data[0]);
}
else if (funcID == MSR_FUNC_POWER)
{
// Round to nearest .0001 to avoid distracting excess precision.
data[0] = round(data[0] * 10000) / 10000;
data[2] = round(data[2] * 10000) / 10000;
ETWMarkCPUPower(MSRName, data[0], data[2]);
//outputPrintf(L"%s Power (W) = %3.2f\n", MSRName, data[0]);
//outputPrintf(L"%s Energy(J) = %3.2f\n", MSRName, data[1]);
//outputPrintf(L"%s Energy(mWh)=%3.2f\n", MSRName, data[2]);
}
else if (funcID == MSR_FUNC_TEMP)
{
// The 3.02 version of Intel Power Gadget seems to report the temperature
// in F instead of C.
ETWMarkCPUTemp(MSRName, data[0], maxTemperature);
//outputPrintf(L"%s Temp (C) = %3.0f (max is %3.0f)\n", MSRName, data[0], (double)maxTemperature);
}
else if (funcID == MSR_FUNC_MAX_POWER)
{
//outputPrintf(L"%s Max Power (W) = %3.0f\n", MSRName, data[0]);
}
else
{
//outputPrintf(L"Unused funcID %d\n", funcID);
}
}
}

void CPowerStatusMonitor::SampleBatteryStat()
{
HDEVINFO hdev = SetupDiGetClassDevs(&GUID_DEVCLASS_BATTERY,
Expand Down Expand Up @@ -183,11 +247,40 @@ void CPowerStatusMonitor::BatteryMonitorThread()
break;

SampleBatteryStat();
SampleCPUPowerState();
}
}

CPowerStatusMonitor::CPowerStatusMonitor()
{
// If Intel Power Gadget is installed then use it to get CPU power data.
#if _M_X64
PCWSTR dllName = L"\\EnergyLib64.dll";
#else
PCWSTR dllName = L"\\EnergyLib32.dll";
#endif
#pragma warning(disable : 4996)
PCWSTR powerGadgetDir = _wgetenv(L"IPG_Dir");
if (powerGadgetDir)
energyLib_ = LoadLibrary((std::wstring(powerGadgetDir) + dllName).c_str());
if (energyLib_)
{
IntelEnergyLibInitialize = (IntelEnergyLibInitialize_t)GetProcAddress(energyLib_, "IntelEnergyLibInitialize");
GetNumMsrs = (GetNumMsrs_t)GetProcAddress(energyLib_, "GetNumMsrs");
GetMsrName = (GetMsrName_t)GetProcAddress(energyLib_, "GetMsrName");
GetMsrFunc = (GetMsrFunc_t)GetProcAddress(energyLib_, "GetMsrFunc");
GetPowerData = (GetPowerData_t)GetProcAddress(energyLib_, "GetPowerData");
ReadSample = (ReadSample_t)GetProcAddress(energyLib_, "ReadSample");
auto GetMaxTemperature = (GetMaxTemperature_t)GetProcAddress(energyLib_, "GetMaxTemperature");
if (GetMaxTemperature)
GetMaxTemperature(0, &maxTemperature);
if (IntelEnergyLibInitialize && ReadSample)
{
IntelEnergyLibInitialize();
ReadSample();
}
}

hExitEvent_ = CreateEvent(nullptr, FALSE, FALSE, nullptr);
hThread_ = CreateThread(NULL, 0, StaticBatteryMonitorThread, this, 0, NULL);
}
Expand Down
19 changes: 19 additions & 0 deletions UIforETW/PowerStatus.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ limitations under the License.

#pragma once

// https://software.intel.com/en-us/blogs/2012/12/13/using-the-intel-power-gadget-api-on-mac-os-x
typedef int(*IntelEnergyLibInitialize_t)();
typedef int(*GetNumMsrs_t)(int* nMsr);
typedef int(*GetMsrName_t)(int iMsr, wchar_t* szName);
typedef int(*GetMsrFunc_t)(int iMsr, int* pFuncID);
typedef int(*GetPowerData_t)(int iNode, int iMsr, double* pResult, int* nResult);
typedef int(*ReadSample_t)();
typedef int(*GetMaxTemperature_t)(int iNode, int* degreeC);

class CPowerStatusMonitor
{
public:
Expand All @@ -27,10 +36,20 @@ class CPowerStatusMonitor
void BatteryMonitorThread();

void SampleBatteryStat();
void SampleCPUPowerState();

HANDLE hThread_;
HANDLE hExitEvent_;

HMODULE energyLib_ = nullptr;
IntelEnergyLibInitialize_t IntelEnergyLibInitialize = nullptr;
GetNumMsrs_t GetNumMsrs = nullptr;
GetMsrName_t GetMsrName = nullptr;
GetMsrFunc_t GetMsrFunc = nullptr;
GetPowerData_t GetPowerData = nullptr;
ReadSample_t ReadSample = nullptr;
int maxTemperature = 0;

CPowerStatusMonitor& operator=(const CPowerStatusMonitor&) = delete;
CPowerStatusMonitor(const CPowerStatusMonitor&) = delete;
};
Binary file modified bin/ETWProviders.dll
Binary file not shown.
Binary file modified bin/ETWProviders64.dll
Binary file not shown.
Binary file modified bin/UIforETW.exe
Binary file not shown.
8 changes: 8 additions & 0 deletions include/ETWProviders/etwprof.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ PLATFORM_INTERFACE void ETWMarkWorkingSet(_In_z_ PCWSTR pProcessName, _In_z_ PCW
// discharge rate from struct BATTERY_STATUS.
PLATFORM_INTERFACE void ETWMarkBatteryStatus(_In_z_ PCSTR powerState, float batteryPercentage, _In_z_ PCSTR rate);

// Record CPU/package frequency, power usage, and temperature. Currently Intel only.
PLATFORM_INTERFACE void ETWMarkCPUFrequency(_In_z_ PCWSTR MSRName, double frequencyMHz);
PLATFORM_INTERFACE void ETWMarkCPUPower(_In_z_ PCWSTR MSRName, double powerW, double energymWh);
PLATFORM_INTERFACE void ETWMarkCPUTemp(_In_z_ PCWSTR MSRName, double tempC, double maxTempC);

// Insert a begin event to mark the start of some work. The return value is a 64-bit
// time stamp which should be passed to the corresponding ETWEnd function.
PLATFORM_INTERFACE int64 ETWBegin( _In_z_ PCSTR pMessage );
Expand Down Expand Up @@ -122,6 +127,9 @@ inline void ETWMarkPrintf( const char *pMessage, ... ) {}
inline void ETWWorkerMarkPrintf( const char *pMessage, ... ) {}
inline void ETWMarkWorkingSet(const wchar_t* pProcessName, const wchar_t* pProcess, unsigned counter, unsigned privateWS, unsigned PSS, unsigned workingSet) {}
inline void ETWMarkBatteryStatus(_In_z_ PCSTR powerState, float batteryPercentage, _In_z_ PCSTR rate) {}
inline void ETWMarkCPUFrequency(_In_z_ PCSTR MSRName, double frequencyMHz) {}
inline void ETWMarkCPUPower(_In_z_ PCSTR MSRName, double powerW, double energymWh) {}
inline void ETWMarkCPUTemp(_In_z_ PCSTR MSRName, double tempC, double maxTempC) {}
inline int64 ETWBegin( const char* ) { return 0; }
inline int64 ETWWorkerBegin( const char* ) { return 0; }
inline int64 ETWEnd( const char*, int64 ) { return 0; }
Expand Down
Binary file modified lib/ETWProviders.lib
Binary file not shown.
Binary file modified lib/ETWProviders64.lib
Binary file not shown.

0 comments on commit 556a82e

Please sign in to comment.