From e99ca98b3a074d05e7c3b6973d63f3bb643d1b8b Mon Sep 17 00:00:00 2001 From: drbregma Date: Wed, 9 Mar 2016 10:54:35 -0800 Subject: [PATCH] initial commit --- PresentMon.cpp | 185 ++++++++++++++++++++++++++ PresentMon.hpp | 63 +++++++++ PresentMon.sln | 28 ++++ PresentMon.vcxproj | 165 ++++++++++++++++++++++++ PresentMon.vcxproj.filters | 15 +++ PresentMonEtw.cpp | 176 +++++++++++++++++++++++++ README.md | 9 ++ TraceSession.cpp | 110 ++++++++++++++++ TraceSession.hpp | 45 +++++++ Util.cpp | 154 ++++++++++++++++++++++ Util.hpp | 26 ++++ license.txt | 258 +++++++++++++++++++++++++++++++++++++ main.cpp | 113 ++++++++++++++++ 13 files changed, 1347 insertions(+) create mode 100644 PresentMon.cpp create mode 100644 PresentMon.hpp create mode 100644 PresentMon.sln create mode 100644 PresentMon.vcxproj create mode 100644 PresentMon.vcxproj.filters create mode 100644 PresentMonEtw.cpp create mode 100644 README.md create mode 100644 TraceSession.cpp create mode 100644 TraceSession.hpp create mode 100644 Util.cpp create mode 100644 Util.hpp create mode 100644 license.txt create mode 100644 main.cpp diff --git a/PresentMon.cpp b/PresentMon.cpp new file mode 100644 index 00000000..7dbbeef2 --- /dev/null +++ b/PresentMon.cpp @@ -0,0 +1,185 @@ +//-------------------------------------------------------------------------------------- +// Copyright 2015 Intel Corporation +// All Rights Reserved +// +// Permission is granted to use, copy, distribute and prepare derivative works of this +// software for any purpose and without fee, provided, that the above copyright notice +// and this statement appear in all copies. Intel makes no representations about the +// suitability of this software for any purpose. THIS SOFTWARE IS PROVIDED "AS IS." +// INTEL SPECIFICALLY DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, AND ALL LIABILITY, +// INCLUDING CONSEQUENTIAL AND OTHER INDIRECT DAMAGES, FOR THE USE OF THIS SOFTWARE, +// INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PROPRIETARY RIGHTS, AND INCLUDING THE +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. Intel does not +// assume any responsibility for any errors which may appear in this software nor any +// responsibility to update it. +//-------------------------------------------------------------------------------------- + +#include "PresentMon.hpp" +#include "Util.hpp" +#include +#include + +#include +#include +#include + +#pragma comment(lib, "psapi.lib") +#pragma comment(lib, "shlwapi.lib") + +enum { + MAX_HISTORY_LENGTH = 60, + CHAIN_TIMEOUT_THRESHOLD_TICKS = 10000 // 10 sec +}; + +template +static void map_erase_if(Map& m, F pred) +{ + typename Map::iterator i = m.begin(); + while ((i = std::find_if(i, m.end(), pred)) != m.end()) + m.erase(i++); +} + +static void UpdateProcessInfo(ProcessInfo& info, uint64_t now, uint32_t thisPid) +{ + if (now - info.mLastRefreshTicks > 1000) { + info.mLastRefreshTicks = now; + char path[MAX_PATH] = ""; + HANDLE h = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, thisPid); + if (h) { + GetModuleFileNameExA(h, NULL, path, sizeof(path) - 1); + std::string name = PathFindFileNameA(path); + if (name != info.mModuleName) { + info.mChainMap.clear(); + info.mModuleName = name; + } + CloseHandle(h); + } else { + info.mChainMap.clear(); + } + } + // remove chains without recent updates + map_erase_if(info.mChainMap, [now](const std::pair& entry) { + return now - entry.second.mLastUpdateTicks > CHAIN_TIMEOUT_THRESHOLD_TICKS; + }); +} + +void AddPresent(PresentMonData& pm, PresentEvent& p, uint64_t now, uint64_t perfFreq) +{ + auto& proc = pm.mProcessMap[p.ProcessId]; + if (!proc.mLastRefreshTicks) { + UpdateProcessInfo(proc, now, p.ProcessId); + } + + if (pm.mArgs->mTargetProcessName && strcmp(pm.mArgs->mTargetProcessName, "*") && + _stricmp(pm.mArgs->mTargetProcessName, proc.mModuleName.c_str())) { + // process name does not match + return; + } + if (pm.mArgs->mTargetPid && p.ProcessId != pm.mArgs->mTargetPid) { + return; + } + + auto& chain = proc.mChainMap[p.SwapChainAddress]; + if (chain.mPresentHistory.size() > MAX_HISTORY_LENGTH) { + chain.mPresentHistory.pop_front(); + } + chain.mPresentHistory.push_back(p); + chain.mLastUpdateTicks = now; + chain.mLastSyncInterval = p.SyncInterval; + chain.mLastFlags = p.PresentFlags; + + if (pm.mOutputFile) { + auto len = chain.mPresentHistory.size(); + if (len > 2) { + auto& curr = chain.mPresentHistory[len - 1]; + auto& prev = chain.mPresentHistory[len - 2]; + double deltaMilliseconds = 1000 * double(curr.QpcTime - prev.QpcTime) / perfFreq; + fprintf(pm.mOutputFile, "%s,%d,0x%016llX,%.3lf,%d,%d\n", + proc.mModuleName.c_str(), p.ProcessId, p.SwapChainAddress, deltaMilliseconds, curr.SyncInterval, curr.PresentFlags); + } + } +} + +static double ComputeFps(SwapChainData& stats, uint64_t qpcFreq) +{ + // TODO: better method + auto start = stats.mPresentHistory.front().QpcTime; + auto end = stats.mPresentHistory.back().QpcTime; + auto count = stats.mPresentHistory.size(); + + double deltaT = double(end - start) / qpcFreq; + return count / deltaT; +} + +void PresentMon_Init(const PresentMonArgs& args, PresentMonData& pm) +{ + pm.mArgs = &args; + + if (args.mOutputFileName) { + fopen_s(&pm.mOutputFile, args.mOutputFileName, "w"); + } else if (args.mTargetProcessName) { + struct tm tm; + time_t time_now = time(NULL); + localtime_s(&tm, &time_now); + std::string date = FormatString("%4d-%02d-%02d-%02d-%02d", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); + std::string path; + if (strchr(args.mTargetProcessName, '*')) { + path = FormatString("PresentMon-%s.csv", date.c_str()); + } else { + path = FormatString("PresentMon-%s-%s.csv", args.mTargetProcessName, date.c_str()); + } + fopen_s(&pm.mOutputFile, path.c_str(), "w"); + } + + if (pm.mOutputFile) { + fprintf(pm.mOutputFile, "module,pid,chain,delta,vsync,flags\n"); + } +} + +void PresentMon_Update(PresentMonData& pm, std::vector presents, uint64_t perfFreq) +{ + std::string display; + uint64_t now = GetTickCount64(); + + // store the new presents into processes + for (auto& p : presents) + { + AddPresent(pm, p, now, perfFreq); + } + + // update all processes + for (auto& proc : pm.mProcessMap) + { + UpdateProcessInfo(proc.second, now, proc.first); + if (proc.second.mModuleName.empty() || + proc.second.mChainMap.empty()) + { + // don't display empty processes + continue; + } + display += FormatString("%s[%d]:\n", proc.second.mModuleName.c_str(),proc.first); + for (auto& chain : proc.second.mChainMap) + { + double fps = ComputeFps(chain.second, perfFreq); + display += FormatString("\t%016llX: SyncInterval %d | Flags %d | %.2lf ms/frame (%.1lf fps)%s\n", + chain.first, chain.second.mLastSyncInterval, chain.second.mLastFlags, 1000.0/fps, fps, + (now - chain.second.mLastUpdateTicks) > 1000 ? " [STALE]" : ""); + } + } + + // refresh the console + ClearConsole(); + printf(display.c_str()); +} + +void PresentMon_Shutdown(PresentMonData& pm) +{ + if (pm.mOutputFile) + { + fclose(pm.mOutputFile); + pm.mOutputFile = nullptr; + } + pm.mProcessMap.clear(); +} diff --git a/PresentMon.hpp b/PresentMon.hpp new file mode 100644 index 00000000..5c0253fe --- /dev/null +++ b/PresentMon.hpp @@ -0,0 +1,63 @@ +//-------------------------------------------------------------------------------------- +// Copyright 2015 Intel Corporation +// All Rights Reserved +// +// Permission is granted to use, copy, distribute and prepare derivative works of this +// software for any purpose and without fee, provided, that the above copyright notice +// and this statement appear in all copies. Intel makes no representations about the +// suitability of this software for any purpose. THIS SOFTWARE IS PROVIDED "AS IS." +// INTEL SPECIFICALLY DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, AND ALL LIABILITY, +// INCLUDING CONSEQUENTIAL AND OTHER INDIRECT DAMAGES, FOR THE USE OF THIS SOFTWARE, +// INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PROPRIETARY RIGHTS, AND INCLUDING THE +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. Intel does not +// assume any responsibility for any errors which may appear in this software nor any +// responsibility to update it. +//-------------------------------------------------------------------------------------- + +#pragma once + +#include +#include +#include +#include +#include + +struct PresentEvent { + uint64_t QpcTime; + uint64_t SwapChainAddress; + uint32_t SyncInterval; + uint32_t PresentFlags; + uint32_t ProcessId; +}; + +struct SwapChainData { + uint64_t mLastUpdateTicks = 0; + uint32_t mLastSyncInterval = -1; + uint32_t mLastFlags = -1; + std::deque mPresentHistory; +}; + +struct ProcessInfo { + uint64_t mLastRefreshTicks = 0; // GetTickCount64 + std::string mModuleName; + std::map mChainMap; +}; + +struct PresentMonArgs { + const char *mOutputFileName = nullptr; + const char *mTargetProcessName = nullptr; + int mTargetPid = 0; +}; + +struct PresentMonData { + const PresentMonArgs *mArgs = nullptr; + FILE *mOutputFile = nullptr; + std::map mProcessMap; +}; + +void PresentMonEtw(PresentMonArgs args); + +void PresentMon_Init(const PresentMonArgs& args, PresentMonData& data); +void PresentMon_Update(PresentMonData& data, std::vector presents, uint64_t perfFreq); +void PresentMon_Shutdown(PresentMonData& data); + diff --git a/PresentMon.sln b/PresentMon.sln new file mode 100644 index 00000000..faeda236 --- /dev/null +++ b/PresentMon.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.24720.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PresentMon", "PresentMon.vcxproj", "{4EB9794B-1F12-48CE-ADC1-917E9810F29E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4EB9794B-1F12-48CE-ADC1-917E9810F29E}.Debug|x64.ActiveCfg = Debug|x64 + {4EB9794B-1F12-48CE-ADC1-917E9810F29E}.Debug|x64.Build.0 = Debug|x64 + {4EB9794B-1F12-48CE-ADC1-917E9810F29E}.Debug|x86.ActiveCfg = Debug|Win32 + {4EB9794B-1F12-48CE-ADC1-917E9810F29E}.Debug|x86.Build.0 = Debug|Win32 + {4EB9794B-1F12-48CE-ADC1-917E9810F29E}.Release|x64.ActiveCfg = Release|x64 + {4EB9794B-1F12-48CE-ADC1-917E9810F29E}.Release|x64.Build.0 = Release|x64 + {4EB9794B-1F12-48CE-ADC1-917E9810F29E}.Release|x86.ActiveCfg = Release|Win32 + {4EB9794B-1F12-48CE-ADC1-917E9810F29E}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/PresentMon.vcxproj b/PresentMon.vcxproj new file mode 100644 index 00000000..751499be --- /dev/null +++ b/PresentMon.vcxproj @@ -0,0 +1,165 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {4EB9794B-1F12-48CE-ADC1-917E9810F29E} + Win32Proj + PresentMon + 8.1 + + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + + + + Level3 + Disabled + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreaded + + + Console + true + true + true + + + + + + Level3 + + + MaxSpeed + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreaded + + + Console + true + true + true + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PresentMon.vcxproj.filters b/PresentMon.vcxproj.filters new file mode 100644 index 00000000..c718210f --- /dev/null +++ b/PresentMon.vcxproj.filters @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PresentMonEtw.cpp b/PresentMonEtw.cpp new file mode 100644 index 00000000..fa75dd88 --- /dev/null +++ b/PresentMonEtw.cpp @@ -0,0 +1,176 @@ +//-------------------------------------------------------------------------------------- +// Copyright 2015 Intel Corporation +// All Rights Reserved +// +// Permission is granted to use, copy, distribute and prepare derivative works of this +// software for any purpose and without fee, provided, that the above copyright notice +// and this statement appear in all copies. Intel makes no representations about the +// suitability of this software for any purpose. THIS SOFTWARE IS PROVIDED "AS IS." +// INTEL SPECIFICALLY DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, AND ALL LIABILITY, +// INCLUDING CONSEQUENTIAL AND OTHER INDIRECT DAMAGES, FOR THE USE OF THIS SOFTWARE, +// INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PROPRIETARY RIGHTS, AND INCLUDING THE +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. Intel does not +// assume any responsibility for any errors which may appear in this software nor any +// responsibility to update it. +//-------------------------------------------------------------------------------------- + +#include "PresentMon.hpp" +#include "TraceSession.hpp" + +#include +#include + +#include +#include + +static auto DXGI_PROVIDER_GUID = L"{CA11C036-0102-4A2D-A6AD-F03CFED5D3C9}"; + +extern bool g_Quit; + +struct DxgiConsumer : ITraceConsumer +{ + CRITICAL_SECTION mMutex; + std::vector mPresents; + + bool DequeuePresents(std::vector& outPresents) + { + if (mPresents.size()) + { + EnterCriticalSection(&mMutex); + outPresents.swap(mPresents); + LeaveCriticalSection(&mMutex); + return !outPresents.empty(); + } + return false; + } + + DxgiConsumer() { + InitializeCriticalSection(&mMutex); + } + ~DxgiConsumer() { + DeleteCriticalSection(&mMutex); + } + + virtual void OnEventRecord(_In_ PEVENT_RECORD pEventRecord); + virtual bool ContinueProcessing() { return !g_Quit; } +}; + +void DxgiConsumer::OnEventRecord(PEVENT_RECORD pEventRecord) +{ + enum { + Present_Start = 42, + Present_Stop = 43, + IDXGISwapChain_Present_Start = 178, + IDXGISwapChain_Present_Stop // useless with multiple chains because it doesn't contain the chain ptr. + }; + + auto hdr = pEventRecord->EventHeader; + + //if (hdr.EventDescriptor.Id != Present_Start) + if (hdr.EventDescriptor.Id != IDXGISwapChain_Present_Start) + { + return; + } + + PresentEvent event = { 0 }; + event.ProcessId = hdr.ProcessId; + event.QpcTime = *(uint64_t*)&pEventRecord->EventHeader.TimeStamp; + + if (pEventRecord->UserDataLength >= 16 && + !(pEventRecord->EventHeader.Flags & EVENT_HEADER_FLAG_32_BIT_HEADER)) + { + struct IDXGISwapChain_Present_Start_Data_64 { + uint64_t SwapChainAddress; + uint32_t SyncInterval; + uint32_t Flags; + }; + auto data = (IDXGISwapChain_Present_Start_Data_64*)pEventRecord->UserData; + event.SwapChainAddress = data->SwapChainAddress; + event.SyncInterval = data->SyncInterval; + event.PresentFlags = data->Flags; + } + else if(pEventRecord->UserDataLength >= 12 && + (pEventRecord->EventHeader.Flags & EVENT_HEADER_FLAG_32_BIT_HEADER)) + { + struct IDXGISwapChain_Present_Start_Data_32 { + uint32_t SwapChainAddress; + uint32_t SyncInterval; + uint32_t Flags; + }; + auto data = (IDXGISwapChain_Present_Start_Data_32*)pEventRecord->UserData; + event.SwapChainAddress = data->SwapChainAddress; + event.SyncInterval = data->SyncInterval; + event.PresentFlags = data->Flags; + } + + EnterCriticalSection(&mMutex); + mPresents.push_back(event); + LeaveCriticalSection(&mMutex); +} + +static void EtwProcessingThread(TraceSession *session) +{ + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); + + session->Process(); +} + +static GUID GuidFromString(const wchar_t *guidString) +{ + GUID g; + auto hr = CLSIDFromString(guidString, &g); + assert(SUCCEEDED(hr)); + return g; +} + +void PresentMonEtw(PresentMonArgs args) +{ + GUID dxgiProvider = GuidFromString(DXGI_PROVIDER_GUID); + + TraceSession session(L"PresentMon"); + DxgiConsumer consumer; + + if (!session.Start()) { + if (session.Status() == ERROR_ALREADY_EXISTS) { + if (!session.Stop() || !session.Start()) { + printf("ETW session error. Quitting.\n"); + exit(0); + } + } + } + + session.EnableProvider(dxgiProvider, TRACE_LEVEL_RESERVED6); + + session.OpenTrace(&consumer); + + { + // Launch the ETW producer thread + std::thread etwThread(EtwProcessingThread, &session); + + // Consume / Update based on the ETW output + { + PresentMonData data; + + PresentMon_Init(args, data); + + while (!g_Quit) + { + std::vector presents; + consumer.DequeuePresents(presents); + + PresentMon_Update(data, presents, session.PerfFreq()); + + Sleep(100); + } + + PresentMon_Shutdown(data); + } + + etwThread.join(); + } + + session.CloseTrace(); + session.DisableProvider(dxgiProvider); + session.Stop(); +} + diff --git a/README.md b/README.md new file mode 100644 index 00000000..2a6f5f59 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# PresentMon +ETW based FPS monitor + +command line options: + -captureall: record ALL processes (default). + -process_name [exe name]: record specific process. + -process_id [integer]: record specific process ID. + -output_file [path]: override the default output path. + -no_csv: do not create any output file. \ No newline at end of file diff --git a/TraceSession.cpp b/TraceSession.cpp new file mode 100644 index 00000000..dd2542a0 --- /dev/null +++ b/TraceSession.cpp @@ -0,0 +1,110 @@ +// Code based on: +// http://chabster.blogspot.com/2012/10/realtime-etw-consumer-howto.html +// + +#include "TraceSession.hpp" +#include +#include + +static VOID WINAPI EventRecordCallback(_In_ PEVENT_RECORD pEventRecord) +{ + reinterpret_cast(pEventRecord->UserContext)->OnEventRecord(pEventRecord); +} + +static ULONG WINAPI BufferRecordCallback(_In_ PEVENT_TRACE_LOGFILE Buffer) +{ + return reinterpret_cast(Buffer->Context)->ContinueProcessing(); +} + +TraceSession::TraceSession(LPCTSTR szSessionName) + : _szSessionName(_tcsdup(szSessionName)) + , _status(0) + , _pSessionProperties(0) + , hSession(0) + , _hTrace(0) +{ +} + +TraceSession::~TraceSession(void) +{ + free(_szSessionName); + free(_pSessionProperties); +} + +bool TraceSession::Start() +{ + if (!_pSessionProperties) { + static_assert(sizeof(EVENT_TRACE_PROPERTIES) == 120, ""); + const ULONG buffSize = sizeof(EVENT_TRACE_PROPERTIES) + (ULONG)(_tcslen(_szSessionName) + 1) * sizeof(TCHAR); + _pSessionProperties = reinterpret_cast(malloc(buffSize)); + ZeroMemory(_pSessionProperties, buffSize); + _pSessionProperties->Wnode.BufferSize = buffSize; + _pSessionProperties->Wnode.ClientContext = 1; + _pSessionProperties->LogFileMode = EVENT_TRACE_REAL_TIME_MODE; + _pSessionProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); + } + + // Create the trace session. + _status = StartTraceW(&hSession, _szSessionName, _pSessionProperties); + + return (_status == ERROR_SUCCESS); +} + +bool TraceSession::EnableProvider(const GUID& providerId, UCHAR level, ULONGLONG anyKeyword, ULONGLONG allKeyword) +{ + _status = EnableTraceEx2(hSession, &providerId, EVENT_CONTROL_CODE_ENABLE_PROVIDER, level, anyKeyword, allKeyword, 0, NULL); + return (_status == ERROR_SUCCESS); +} + +bool TraceSession::OpenTrace(ITraceConsumer *pConsumer) +{ + if (!pConsumer) + return false; + + ZeroMemory(&_logFile, sizeof(EVENT_TRACE_LOGFILE)); + _logFile.LoggerName = _szSessionName; + _logFile.ProcessTraceMode = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD | PROCESS_TRACE_MODE_RAW_TIMESTAMP; + _logFile.EventRecordCallback = &EventRecordCallback; + _logFile.BufferCallback = &BufferRecordCallback; + _logFile.Context = pConsumer; + + _hTrace = ::OpenTraceW(&_logFile); + return (_hTrace != 0); +} + +bool TraceSession::Process() +{ + _status = ProcessTrace(&_hTrace, 1, NULL, NULL); + return (_status == ERROR_SUCCESS); +} + +bool TraceSession::CloseTrace() +{ + _status = ::CloseTrace(_hTrace); + return (_status == ERROR_SUCCESS); +} + +bool TraceSession::DisableProvider(const GUID& providerId) +{ + _status = EnableTraceEx2(hSession, &providerId, EVENT_CONTROL_CODE_DISABLE_PROVIDER, 0, 0, 0, 0, NULL); + return (_status == ERROR_SUCCESS); +} + +bool TraceSession::Stop() +{ + _status = ControlTraceW(hSession, _szSessionName, _pSessionProperties, EVENT_TRACE_CONTROL_STOP); + delete _pSessionProperties; + _pSessionProperties = NULL; + + return (_status == ERROR_SUCCESS); +} + +ULONG TraceSession::Status() const +{ + return _status; +} + +LONGLONG TraceSession::PerfFreq() const +{ + return _logFile.LogfileHeader.PerfFreq.QuadPart; +} \ No newline at end of file diff --git a/TraceSession.hpp b/TraceSession.hpp new file mode 100644 index 00000000..28860f71 --- /dev/null +++ b/TraceSession.hpp @@ -0,0 +1,45 @@ +// Code based on: +// http://chabster.blogspot.com/2012/10/realtime-etw-consumer-howto.html +// + +#pragma once + +#include +#include // must be after windows.h +#include // must be after windows.h +#include // must be after windows.h + +#undef OpenTrace + +struct ITraceConsumer { + virtual void OnEventRecord(_In_ PEVENT_RECORD pEventRecord) = 0; + virtual bool ContinueProcessing() = 0; +}; + +class TraceSession +{ + +public: + TraceSession(LPCTSTR szSessionName); + ~TraceSession(); + +public: + bool Start(); + bool EnableProvider(const GUID& providerId, UCHAR level, ULONGLONG anyKeyword = 0, ULONGLONG allKeyword = 0); + bool OpenTrace(ITraceConsumer *pConsumer); + bool Process(); + bool CloseTrace(); + bool DisableProvider(const GUID& providerId); + bool Stop(); + + ULONG Status() const; + LONGLONG PerfFreq() const; + +private: + LPTSTR _szSessionName; + ULONG _status; + EVENT_TRACE_PROPERTIES* _pSessionProperties; + TRACEHANDLE hSession; + EVENT_TRACE_LOGFILEW _logFile; + TRACEHANDLE _hTrace; +}; diff --git a/Util.cpp b/Util.cpp new file mode 100644 index 00000000..cbef6618 --- /dev/null +++ b/Util.cpp @@ -0,0 +1,154 @@ +//-------------------------------------------------------------------------------------- +// Copyright 2015 Intel Corporation +// All Rights Reserved +// +// Permission is granted to use, copy, distribute and prepare derivative works of this +// software for any purpose and without fee, provided, that the above copyright notice +// and this statement appear in all copies. Intel makes no representations about the +// suitability of this software for any purpose. THIS SOFTWARE IS PROVIDED "AS IS." +// INTEL SPECIFICALLY DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, AND ALL LIABILITY, +// INCLUDING CONSEQUENTIAL AND OTHER INDIRECT DAMAGES, FOR THE USE OF THIS SOFTWARE, +// INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PROPRIETARY RIGHTS, AND INCLUDING THE +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. Intel does not +// assume any responsibility for any errors which may appear in this software nor any +// responsibility to update it. +//-------------------------------------------------------------------------------------- + +#include +#include + +std::string FormatString(const char *fmt, ...) +{ + char buf[4096]; + va_list args; + va_start(args, fmt); + vsnprintf_s(buf, sizeof(buf), fmt, args); + va_end(args); + return buf; +} + +void ClearConsole() +{ + HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + COORD coordScreen = { 0, 0 }; // home for the cursor + DWORD cCharsWritten; + CONSOLE_SCREEN_BUFFER_INFO csbi; + DWORD dwConSize; + + // Get the number of character cells in the current buffer. + + if (!GetConsoleScreenBufferInfo(hConsole, &csbi)) + { + return; + } + + dwConSize = csbi.dwSize.X * csbi.dwSize.Y; + + // Fill the entire screen with blanks. + + if (!FillConsoleOutputCharacter(hConsole, // Handle to console screen buffer + (TCHAR) ' ', // Character to write to the buffer + dwConSize, // Number of cells to write + coordScreen, // Coordinates of first cell + &cCharsWritten))// Receive number of characters written + { + return; + } + + // Get the current text attribute. + + if (!GetConsoleScreenBufferInfo(hConsole, &csbi)) + { + return; + } + + // Set the buffer's attributes accordingly. + + if (!FillConsoleOutputAttribute(hConsole, // Handle to console screen buffer + csbi.wAttributes, // Character attributes to use + dwConSize, // Number of cells to set attribute + coordScreen, // Coordinates of first cell + &cCharsWritten)) // Receive number of characters written + { + return; + } + + // Put the cursor at its home coordinates. + + SetConsoleCursorPosition(hConsole, coordScreen); +} + +bool HaveAdministratorPrivilidges( + void) +{ + static int elevated = -1; + + if (elevated == -1) + { + elevated = 0; + + typedef BOOL(WINAPI *OpenProcessTokenProc)(HANDLE ProcessHandle, DWORD DesiredAccess, PHANDLE TokenHandle); + typedef BOOL(WINAPI *GetTokenInformationProc)(HANDLE TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass, LPVOID TokenInformation, DWORD TokenInformationLength, DWORD *ReturnLength); + HMODULE advapi = LoadLibraryA("advapi32"); + if (advapi) + { + OpenProcessTokenProc OpenProcessToken = (OpenProcessTokenProc)GetProcAddress(advapi, "OpenProcessToken"); + GetTokenInformationProc GetTokenInformation = (GetTokenInformationProc)GetProcAddress(advapi, "GetTokenInformation"); + if (OpenProcessToken && GetTokenInformation) + { + HANDLE hToken = NULL; + if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) + { + //TOKEN_ELEVATION is not defined in the vs2003 headers, so this is an easy workaround -- + // the struct just has a single DWORD member anyways, so we can use DWORD instead of TOKEN_ELEVATION. + DWORD dwSize; + /*TOKEN_ELEVATION*/ DWORD TokenIsElevated; + if (GetTokenInformation(hToken, (TOKEN_INFORMATION_CLASS)20 /*TokenElevation*/, &TokenIsElevated, sizeof(TokenIsElevated), &dwSize)) + { + elevated = TokenIsElevated; + } + CloseHandle(hToken); + } + } + FreeLibrary(advapi); + } + } + + return elevated == 1; +} + +void RestartAsAdministrator( + int argc, char **argv) +{ + std::string command = FormatString("-waitpid %d", GetCurrentProcessId()); + char exe_path[MAX_PATH]; + + for (int i = 0; i < argc; ++i) { + const char *arg = argv[i]; + if (arg[0] != '\"' && strchr(arg, ' ')) { + command += FormatString(" \"%s\"", arg); + } else { + command += FormatString(" %s", arg); + } + } + + GetModuleFileNameA(NULL, exe_path, sizeof(exe_path)); + if ((HINSTANCE)SE_ERR_ACCESSDENIED != ShellExecuteA(NULL, "runas", exe_path, command.c_str(), NULL, SW_SHOW)) + { + // success - kill process. + ExitProcess(0); + } +} + +void WaitForProcess( + int pid) +{ + HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, pid); + if (!hProcess) + { + return; + } + + WaitForSingleObject(hProcess, INFINITE); + CloseHandle(hProcess); +} diff --git a/Util.hpp b/Util.hpp new file mode 100644 index 00000000..75f08729 --- /dev/null +++ b/Util.hpp @@ -0,0 +1,26 @@ +//-------------------------------------------------------------------------------------- +// Copyright 2015 Intel Corporation +// All Rights Reserved +// +// Permission is granted to use, copy, distribute and prepare derivative works of this +// software for any purpose and without fee, provided, that the above copyright notice +// and this statement appear in all copies. Intel makes no representations about the +// suitability of this software for any purpose. THIS SOFTWARE IS PROVIDED "AS IS." +// INTEL SPECIFICALLY DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, AND ALL LIABILITY, +// INCLUDING CONSEQUENTIAL AND OTHER INDIRECT DAMAGES, FOR THE USE OF THIS SOFTWARE, +// INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PROPRIETARY RIGHTS, AND INCLUDING THE +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. Intel does not +// assume any responsibility for any errors which may appear in this software nor any +// responsibility to update it. +//-------------------------------------------------------------------------------------- + +#pragma once + +#include + +std::string FormatString(const char *fmt, ...); +void ClearConsole(); + +bool HaveAdministratorPrivilidges(void); +void RestartAsAdministrator(int argc, char **argv); +void WaitForProcess(int pid); diff --git a/license.txt b/license.txt new file mode 100644 index 00000000..1bb870cb --- /dev/null +++ b/license.txt @@ -0,0 +1,258 @@ +Code Samples License Agreement (Version December 2015) + +IMPORTANT - READ BEFORE COPYING, INSTALLING OR USING. Do not copy, install or +use the Materials (as defined below) provided under this license agreement +("Agreement") from Intel Corporation ("Intel"), until you ("You") have carefully +read the following terms and conditions. By copying, installing or otherwise +using the Materials, You agree to be bound by the terms of this Agreement. If +You do not agree to the terms of this Agreement, do not copy, install or use the +Materials. + +If You are agreeing to the terms and conditions of this Agreement on behalf of a +company or other legal entity ("Legal Entity"), You represent and warrant that +You have the legal authority to bind that Legal Entity to the Agreement, in +which case, "You" or "Your" will mean such Legal Entity. + +By agreeing to this Agreement, You affirm that You are of legal age (18 years +old or older) to enter into this Agreement. If You are not of legal age You may +not enter into this Agreement, and either Your parent, legal guardian or Legal +Entity must agree to the terms and conditions of this Agreement and enter into +this Agreement, in which case, "You" or "Your" will mean such parent, legal +guardian, or Legal Entity. + +Third Party Programs (as defined below), even if included with the distribution +of the Materials, are governed by separate third party license terms, including +without limitation, open source software license terms. Such third party license +terms (and not this Agreement) govern Your use of the Third Party Programs, and +Intel is not liable for the Third Party Programs. + +1. LICENSE DEFINITIONS: + +"Licensed Patent Claims" means the claims of Intel's patents that are +necessarily and directly infringed by the reproduction and distribution of the +Materials that is authorized in Section 2 below, when the Materials are in its +unmodified form as delivered by Intel to You and not modified or combined with +anything else. Licensed Patent Claims are only those claims that Intel can +license without paying, or getting the consent of, a third party. + +"Materials" means Sample Source Code, Redistributables, and End-User +Documentation but do not include Third Party Programs. + +"Sample Source Code" means Source Code files that are identified as sample code +and which may include example interface or application source code, and any +updates, provided under this Agreement. + +"Source Code" is defined as the software (and not documentation or text) portion +of the Materials provided in human readable format, and includes modifications +that You make or are made on Your behalf as expressly permitted under the terms +of this Agreement. + +"Redistributables" means header, library, and dynamically linkable library +files, and any updates, provided under this Agreement. + +"Third Party Programs" (if any) are the third party software files that may be +included with the Materials for the applicable software that include a separate +third party license agreement in an attached text file. + +"End-User Documentation" means textual materials intended for end users relating +to the Materials. + +2. LICENSE GRANT: + +Subject to the terms and conditions of this Agreement, Intel grants You a +non-exclusive, worldwide, non-assignable, royalty-free limited right and +license: +A. under its copyrights, to: + 1) Copy, modify, and compile the Sample Source Code and distribute it solely + in Your products in executable and source code form; + 2) Copy and distribute the Redistributables solely with Your products; + 3) Copy, modify, and distribute the End User Documentation solely with Your + products. + +B. Under its patents, to: + 1) make copies of the Materials internally only; + 2) use the Materials internally only; and 3) offer to distribute, and + distribute, but not sell, the Materials only as part of or with Your + products, under Intel's copyright license granted in Section 2(A) but only + under the terms of that copyright license and not as a sale (but this + right does not include the right to sub-license); + 4) provided, further, that the license under the Licensed Patent Claims does + not and will not apply to any modifications to, or derivative works of, + the Materials, whether made by You, Your end user (which, for all purposes + under this Agreement, will mean either an end user, a customer, reseller, + distributor or other channel partner), or any third party even if the + modification and creation of derivative works are permitted under 2(A). + +3. LICENSE RESTRICTIONS: + +Except as expressly provided in this Agreement, You may not: +i. use, copy, distribute or publicly display the Materials; +ii. reverse-assemble, reverse-compile, or otherwise reverse-engineer any + software provided solely in binary form, +iii. rent or lease the Materials to any third party; +iv. assign this Agreement or display the Materials; +v. assign this Agreement or transfer the Materials; +vi. modify, adapt or translate the Materials in whole or in part; +vii. distribute, sublicense or transfer the source code form of the Materials + or derivatives thereof to any third party; +viii. distribute the Materials except as part of Your products; +ix. include the Materials in malicious, deceptive, or unlawful programs or + products; +x. modify, create a derivative work, link or distribute the Materials so that + any part of it becomes subject to an Excluded License. + +Upon Intel's release of an update, upgrade, or new version of the Materials, you +will make reasonable efforts to discontinue distribution of the enclosed +Materials and you will make reasonable efforts to distribute such updates, +upgrades, or new versions to your customers who have received the Materials +herein. + +Distribution of the Materials is also subject to the following limitations. +You: +i. will be solely responsible to your customers for any update or support + obligation or other liability which may arise from the distribution; +ii. will not make any statement that your product is "certified", or that its + performance is guaranteed, by Intel; +iii. will not use Intel's name or trademarks to market your product without + written permission; +iv. will prohibit disassembly and reverse engineering of the Materials + provided in executable form; +v. will not publish reviews of Materials without written permission by Intel, + and +vi. will indemnify, hold harmless, and defend Intel and its suppliers from and + against any claims or lawsuits, including attorney's fees, that arise or + result from your distribution of any product. + +4. OWNERSHIP: + +Title to the Materials and all copies thereof remain with Intel or its +suppliers. The Materials are copyrighted and are protected by United States +copyright laws and international treaty provisions. You will not remove any +copyright notice from the Materials. You agree to prevent unauthorized copying +of the Materials. Except as expressly provided herein, Intel does not grant any +express or implied right to you under Intel patents, copyrights, trademarks, or +trade secret information. + +5. NO WARRANTY AND NO SUPPORT: + +Disclaimer. Intel disclaims all warranties of any kind and the terms and +remedies provided in this Agreement are instead of any other warranty or +condition, express, implied or statutory, including those regarding +merchantability, fitness for any particular purpose, non-infringement or any +warranty arising out of any course of dealing, usage of trade, proposal, +specification or sample. Intel does not assume (and does not authorize any +person to assume on its behalf) any other liability. + +Intel may make changes to the Materials, or to items referenced therein, at any +time without notice, but is not obligated to support, update or provide training +for the Materials. Intel may in its sole discretion offer such support, update +or training services under separate terms at Intel's then-current rates. You +may request additional information on Intel's service offerings from an Intel +sales representative. + +6. USER SUBMISSIONS: + +You agree that any material, information, or other communication, including all +data, images, sounds, text, and other things embodied therein, you transmit or +post to an Intel website will be considered non-confidential ("Communications"). +Intel will have no confidentiality obligations with respect to the +Communications. You agree that Intel and its designees will be free to copy, +modify, create derivative works, publicly display, disclose, distribute, license +and sublicense through multiple tiers of distribution and licensees, +incorporate, and otherwise use the Communications, including derivative works +thereto, for any and all commercial or non-commercial purposes. + +7.LIMITATION OF LIABILITY: + +Neither Intel nor its suppliers shall be liable for any damages whatsoever +(including, without limitation, damages for loss of business profits, business +interruption, loss of business information, or other loss) arising out of the +use of or inability to use the Materials, even if Intel has been advised of the +possibility of such damages. Because some jurisdictions prohibit the exclusion +or limitation of liability for consequential or incidental damages, the above +limitation may not apply to You. + +8. TERM AND TERMINATION: + +This Agreement commences upon Your copying, installing or using the Materials +and continues until terminated. Either You or Intel may terminate this +Agreement at any time upon 30 days prior written notice to the other party. +Intel may terminate this license at any time if you are in breach of any of its +terms and conditions. Upon termination, You will immediately destroy the +Materials or return all copies of the Materials to Intel along with any copies +You have made. After termination, the license grant to any Materials or +Redistributables distributed by You in accordance with the terms and conditions +of this Agreement, prior to the effective date of such termination, will survive +any such termination of this Agreement. + +9. U.S. GOVERNMENT RESTRICTED RIGHTS: + +The technical data and computer software covered by this license is a +"Commercial Item", as such term is defined by the FAR 2.101 (48 C.F.R. 2.101) +and is "commercial computer software" and "commercial computer software +documentation" as specified under FAR 12.212 (48 C.F.R. 12.212) or DFARS +227.7202 (48 C.F.R. 227.7202), as applicable. This commercial computer software +and related documentation is provided to end users for use by and on behalf of +the U.S. Government, with only those rights as are granted to all other end +users pursuant to the terms and conditions herein. Use for or on behalf of the +U.S. Government is permitted only if the party acquiring or using this software +is properly authorized by an appropriate U.S. Government official. This use by +or for the U.S. Government clause is in lieu of, and supersedes, any other FAR, +DFARS, or other provision that addresses Government rights in the computer +software or documentation covered by this license. All copyright licenses +granted to the U.S. Government are coextensive with the technical data and +computer software licenses granted herein. The U.S. Government will only have +the right to reproduce, distribute, perform, display, and prepare derivative +works as needed to implement those rights. + +10. APPLICABLE LAWS: + +All disputes arising out of or related to this Agreement, whether based on +contract, tort, or any other legal or equitable theory, will in all respects be +governed by, and construed and interpreted under, the laws of the United States +of America and the State of Delaware, without reference to conflict of laws +principles. The parties agree that the United Nations Convention on Contracts +for the International Sale of Goods (1980) is specifically excluded from and +will not apply to this Agreement. All disputes arising out of or related to this +Agreement, whether based on contract, tort, or any other legal or equitable +theory, will be subject to the exclusive jurisdiction of the courts of the State +of Delaware or of the Federal courts sitting in that State. Each party submits +to the personal jurisdiction of those courts and waives all objections to that +jurisdiction and venue for those disputes. + +10. SEVERABILITY: + +The parties intend that if a court holds that any provision or part of this +Agreement is invalid or unenforceable under applicable law, the court will +modify the provision to the minimum extent necessary to make it valid and +enforceable, or if it cannot be made valid and enforceable, the parties intend +that the court will sever and delete the provision or part from this Agreement. +Any change to or deletion of a provision or part of this Agreement under this +Section will not affect the validity or enforceability of the remainder of this +Agreement, which will continue in full force and effect. + +11. EXPORT: + +You must comply with all laws and regulations of the United States and other +countries governing the export, re-export, import, transfer, distribution, use, +and servicing of Software. In particular, You must not: (a) sell or transfer +Software to a country subject to sanctions, or to any entity listed on a denial +order published by the United States government or any other relevant +government; or (b) use, sell, or transfer Software for the development, design, +manufacture, or production of nuclear, missile, chemical or biological weapons, +or for any other purpose prohibited by the United States government or other +applicable government; without first obtaining all authorizations required by +all applicable laws. For more details on Your export obligations, please visit +http://www.intel.com/content/www/us/en/legal/export-compliance.html?wapkw=export. + +12. ENTIRE AGREEMENT: + +This Agreement contains the complete and exclusive agreement and understanding +between the parties concerning the subject matter of this Agreement, and +supersedes all prior and contemporaneous proposals, agreements, understanding, +negotiations, representations, warranties, conditions, and communications, oral +or written, between the parties relating to the same subject matter. No +modification or amendment to this Agreement will be effective unless in writing +and signed by authorized representatives of each party, and must specifically +identify this Agreement. + diff --git a/main.cpp b/main.cpp new file mode 100644 index 00000000..1cc178df --- /dev/null +++ b/main.cpp @@ -0,0 +1,113 @@ +//-------------------------------------------------------------------------------------- +// Copyright 2015 Intel Corporation +// All Rights Reserved +// +// Permission is granted to use, copy, distribute and prepare derivative works of this +// software for any purpose and without fee, provided, that the above copyright notice +// and this statement appear in all copies. Intel makes no representations about the +// suitability of this software for any purpose. THIS SOFTWARE IS PROVIDED "AS IS." +// INTEL SPECIFICALLY DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, AND ALL LIABILITY, +// INCLUDING CONSEQUENTIAL AND OTHER INDIRECT DAMAGES, FOR THE USE OF THIS SOFTWARE, +// INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PROPRIETARY RIGHTS, AND INCLUDING THE +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. Intel does not +// assume any responsibility for any errors which may appear in this software nor any +// responsibility to update it. +//-------------------------------------------------------------------------------------- + +#include +#include +#include + +#include "PresentMon.hpp" +#include "Util.hpp" + +bool g_Quit = false; +static std::thread *g_PresentMonThread; + +BOOL WINAPI HandlerRoutine( + _In_ DWORD dwCtrlType + ) +{ + g_Quit = true; + if (g_PresentMonThread) { + g_PresentMonThread->join(); + } + exit(0); + return TRUE; +} + +int main(int argc, char ** argv) +{ + --argc; + ++argv; + + if (argc == 0) { + printf( + "command line options:\n" + " -captureall: record ALL processes (default).\n" + " -process_name [exe name]: record specific process.\n" + " -process_id [integer]: record specific process ID.\n" + " -output_file [path]: override the default output path.\n" + " -no_csv: do not create any output file.\n" + ); + return 0; + } + + int waitpid = -1; + PresentMonArgs args; + + args.mTargetProcessName = "*"; + + for (int i = 0; i < argc; ++i) + { + // 2-component arguments + if (i + 1 < argc) + { + if (!strcmp(argv[i], "-waitpid")) + { + waitpid = atoi(argv[++i]); + } + else if (!strcmp(argv[i], "-process_name")) + { + args.mTargetProcessName = argv[++i]; + } + else if (!strcmp(argv[i], "-process_id")) + { + args.mTargetPid = atoi(argv[++i]); + } + else if (!strcmp(argv[i], "-output_file")) + { + args.mOutputFileName = argv[++i]; + } + } + // 1-component args + else + { + if (!strcmp(argv[i], "-no_csv")) + { + args.mOutputFileName = "*"; + } + } + } + + if (waitpid >= 0) { + WaitForProcess(waitpid); + if (!HaveAdministratorPrivilidges()) { + printf("Elevation process failed. Aborting.\n"); + return 0; + } + } + + if (!HaveAdministratorPrivilidges()) { + printf("Process is not running as admin. Attempting to elevate.\n"); + RestartAsAdministrator(argc, argv); + return 0; + } + + SetConsoleCtrlHandler(HandlerRoutine, TRUE); + + // Run PM in a separate thread so we can join it in the CtrlHandler (can't join the main thread) + std::thread pm(PresentMonEtw, args); + g_PresentMonThread = ± + Sleep(INFINITE); +} \ No newline at end of file