Skip to content

Commit

Permalink
Enhanced heap tracing abilities
Browse files Browse the repository at this point in the history
ETW heap tracing can be enabled in *three* different ways and UIforETW
was only supporting one of them. The others can be extremely useful so
this change adds support for them, by overloading the Heap-profiled
processes field. The options are:

1) Heap tracing can be enabled for processes with a particular name.
UIforETW will set the registry key to enable this and all processes
with that name that are launched while the registry key is set will
have heap tracing enabled. This is the original option that UIforETW
supported.

2) Heap tracing can be enabled for one or two PIDs - this lets heap
profiling be enabled for an already running process.

3) Heap tracing can be enabled for a particular process that ETW
will launch when tracing starts.

The first one is enabled by having a (semi-colon delimited) list of
process names to enable heap tracing for - usually just a single name
like chrome.exe

The second one is enabled by having a (semi-colon delimited) list of
process IDs. The maximum number supported is just two.

The third one is enabled by specifying the full path to an executable
such as C:\Windows\System32\notepad.exe.

UIforETW differentiates between these by looking for a backslash
character or a leading digit. These options cannot be used together.

That is, the heap tracing options can be specified as a semi-colon
separated list of process IDs (maximum of two) and process names *or* a
single fully-qualified path name.

ParseHeapTracingSettings parses the users settings, and then heap
tracing is started in one of the three possible ways.

For more details see my blog post on this topic which I promise to
update:

https://randomascii.wordpress.com/2015/04/27/etw-heap-tracingevery-allocation-recorded/

Other changes:
- StartEventThreads was moved so that the threads aren't started until
after most error handling has been done.
- Heap tracing was changed to not record other kernel data, in order to
keep the heap traces as small as possible.
- The tool tip for the Heap-profiled processes field in the settings
dialog was updated to try to explain all of this.
  • Loading branch information
randomascii committed Oct 3, 2018
1 parent a34225a commit 5618e5d
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 12 deletions.
11 changes: 7 additions & 4 deletions UIforETW/Settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,13 @@ BOOL CSettings::OnInitDialog()
toolTip_.SetMaxTipWidth(400);
toolTip_.Activate(TRUE);

toolTip_.AddTool(&btHeapTracingExe_, L"Specify the file names of the exes to be heap traced, "
L"separated by semi-colons. "
L"Enter just the file parts (with the .exe extension) not a full path. For example, "
L"'chrome.exe;notepad.exe'. This is for use with the heap-tracing-to-file mode.");
toolTip_.AddTool(&btHeapTracingExe_, L"Specify which processes to heap trace when using "
L"heap-tracing-to-file mode. Three different methods can be used to specify the processes:\n"
L"1) A semi-colon separated list of process names, such as 'chrome.exe;notepad.exe'. "
L"The processes must be launched after this is set and heap-tracing-to-file is selected.\n"
L"2) A semi-colon separated list of process IDs (PIDs) - maximum of two - such as '1234;5678'.\n"
L"3) A fully specified path to an executable that will be launched by ETW when heap tracing is "
L"started.");
toolTip_.AddTool(&btExtraKernelFlags_, L"Extra kernel flags, separated by '+', such as "
L"\"REGISTRY+PERF_COUNTER\". See \"xperf -providers k\" for the full list. "
L"Note that incorrect kernel flags will cause tracing to fail to start.");
Expand Down
77 changes: 69 additions & 8 deletions UIforETW/UIforETWDlg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -963,13 +963,37 @@ void CUIforETWDlg::StopEventThreads()
void CUIforETWDlg::OnBnClickedStarttracing()
{
RegisterProviders();
StartEventThreads();
if (tracingMode_ == kTracingToMemory)
outputPrintf(L"\nStarting tracing to in-memory circular buffers...\n");
else if (tracingMode_ == kTracingToFile)
outputPrintf(L"\nStarting tracing to disk...\n");
else if (tracingMode_ == kHeapTracingToFile)
outputPrintf(L"\nStarting heap tracing to disk of %s...\n", heapTracingExes_.c_str());
{
auto heapSettings = ParseHeapTracingSettings(heapTracingExes_);
if (heapSettings.pathName.size())
{
outputPrintf(L"");
// Launch and heap-profile the specified process, handy for heap-profiling
// the browser process from startup.
outputPrintf(L"\nLaunching and heap tracing to disk %s...\n", heapSettings.pathName.c_str());
}
else if (heapSettings.processIDs.size())
{
// Heap profile the processes specified by the PIDs (maximum of two).
outputPrintf(L"\nStarting heap tracing to disk of PIDs %s...\n", heapSettings.processIDs.c_str());
}
else
{
std::wstring processNames;
for (auto& name : heapSettings.processNames)
{
if (processNames.size() > 0)
processNames += L" and ";
processNames += name;
}
outputPrintf(L"\nStarting heap tracing to disk of the %s processes...\n", processNames.c_str());
}
}
else
UIETWASSERT(0);

Expand All @@ -983,15 +1007,24 @@ void CUIforETWDlg::OnBnClickedStarttracing()
}

std::wstring kernelProviders = L" Latency+POWER+DISPATCHER+DISK_IO_INIT+FILE_IO+FILE_IO_INIT+VIRT_ALLOC+MEMINFO";
bool cswitch_and_profile = true;
if (tracingMode_ == kHeapTracingToFile)
{
// Latency = PROC_THREAD+LOADER+DISK_IO+HARD_FAULTS+DPC+INTERRUPT+CSWITCH+PROFILE,
// but we don't need all that for heap tracing. The minimum set is PROC_THREAD+LOADER
// and we add on VIRT_ALLOC+MEMINFO because that seems appropriate for heap tracing.
kernelProviders = L" PROC_THREAD+LOADER+VIRT_ALLOC+MEMINFO";
cswitch_and_profile = false;
}
if (!extraKernelFlags_.empty())
kernelProviders += L"+" + extraKernelFlags_;
std::wstring kernelStackWalk;
// Record CPU sampling call stacks, from the PROFILE provider
if (bSampledStacks_)
if (bSampledStacks_ && cswitch_and_profile)
kernelStackWalk += L"+Profile";
// Record context-switch (switch in) and readying-thread (SetEvent, etc.)
// call stacks from DISPATCHER provider.
if (bCswitchStacks_)
if (bCswitchStacks_ && cswitch_and_profile)
kernelStackWalk += L"+CSwitch+ReadyThread";
// Record VirtualAlloc call stacks from the VIRT_ALLOC provider. Also
// record VirtualFree to allow investigation of memory leaks, even though
Expand Down Expand Up @@ -1045,7 +1078,6 @@ void CUIforETWDlg::OnBnClickedStarttracing()
catch (const std::exception& e)
{
outputPrintf(L"Check the extra user providers; failed to translate them from the TraceLogging name to a GUID.\n%hs\n", e.what());
StopEventThreads();
return;
}
}
Expand Down Expand Up @@ -1102,7 +1134,7 @@ void CUIforETWDlg::OnBnClickedStarttracing()
// CLR runtime provider
// https://msdn.microsoft.com/en-us/library/ff357718(v=vs.100).aspx
userProviders += L"+e13c0d23-ccbc-4e12-931b-d9cc2eee27e4:0x1CCBD:0x5";

// note: this seems to be an updated version of
// userProviders += L"+ClrAll:0x98:5";
// which results in Invalid flags. (0x3ec) when I run it
Expand All @@ -1126,7 +1158,33 @@ void CUIforETWDlg::OnBnClickedStarttracing()
std::wstring heapStackWalk;
if (bHeapStacks_)
heapStackWalk = L" -stackwalk HeapCreate+HeapDestroy+HeapAlloc+HeapRealloc";
const std::wstring heapArgs = L" -start UIforETWHeapSession -heap -Pids 0" + heapStackWalk + heapBuffers + heapFile;
auto heapSettings = ParseHeapTracingSettings(heapTracingExes_);
std::wstring heapArgs;
if (heapSettings.pathName.size())
{
// Launch and heap-profile the specified process, handy for heap-profiling
// the browser process from startup.
heapArgs = L" -start UIforETWHeapSession -heap -PidNewProcess \"" + heapSettings.pathName + L"\"" + heapStackWalk + heapBuffers + heapFile;
}
else if (heapSettings.processIDs.size())
{
// Heap profile the processes specified by the PIDs (maximum of two).
heapArgs = L" -start UIforETWHeapSession -heap -Pids " + heapSettings.processIDs + heapStackWalk + heapBuffers + heapFile;
}
else if (heapSettings.processNames.size())
{
// Heap profile the processes specified by heapSettings.processNames that
// were launched when the registry key was set (when "Heap tracing to file"
// was the selected tracing type).
heapArgs = L" -start UIforETWHeapSession -heap -Pids 0" + heapStackWalk + heapBuffers + heapFile;
}
else
{
outputPrintf(L"Error: no heap-profiled processes settings found. Go to the Settings dialog to configure them.\n");
return;
}

StartEventThreads();

bPreTraceRecorded_ = false;

Expand Down Expand Up @@ -1836,7 +1894,10 @@ void CUIforETWDlg::SetHeapTracing(bool forceOff)
DWORD tracingFlags = tracingMode_ == kHeapTracingToFile ? 1 : 0;
if (forceOff)
tracingFlags = 0;
for (const auto& tracingName : split(heapTracingExes_, ';'))

auto heapSettings = ParseHeapTracingSettings(heapTracingExes_);

for (const auto& tracingName : heapSettings.processNames)
{
std::wstring targetKey = L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options";
CreateRegistryKey(HKEY_LOCAL_MACHINE, targetKey, tracingName);
Expand Down
55 changes: 55 additions & 0 deletions UIforETW/Utility.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -974,3 +974,58 @@ void MoveControl(const CWnd* pParent, CWnd& control, int xDelta, int yDelta)
control.SetWindowPos(nullptr, p.x + xDelta, p.y + yDelta, 0, 0, flags);
}
#endif

// Parse the semi-colon separated heap trace settings
HeapTracedProcesses ParseHeapTracingSettings(std::wstring heapTracingExes)
{
HeapTracedProcesses result;
for (const auto& tracingName : split(heapTracingExes, ';'))
{
if (tracingName.size())
{
auto* p = tracingName.c_str();
// If the first character is a digit then assume that it's a PID.
if (iswdigit(p[0]))
{
if (wcschr(p, L' '))
{
outputPrintf(L"Error: don't use space separators between PIDs for heap tracing - use semicolons.\n");
continue;
}
// Convert to space separated PIDs because that is what the xperf -Pids
// option expects.
if (result.processIDs.size() > 0)
result.processIDs += L' ';
result.processIDs += tracingName;
}
else if (wcschr(p, '\\'))
{
// It must be a full path name.
result.pathName = tracingName;
}
else
{
if (wcschr(p, L' '))
{
outputPrintf(L"Error: don't use space separators between process names for heap tracing - use semicolons.\n");
continue;
}
result.processNames.push_back(tracingName);
}
}
}

// Since the three types of heap profiling are mutually exclusive, clear the
// ones that will not be used, to ensure consistency.
if (result.pathName.size())
{
result.processIDs = L"";
result.processNames.clear();
}
else if (result.processIDs.size())
{
result.processNames.clear();
}

return result;
}
33 changes: 33 additions & 0 deletions UIforETW/Utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -203,3 +203,36 @@ void CloseValidHandle(_In_ _Pre_valid_ _Post_ptr_invalid_ HANDLE handle) noexcep
// Put MFC specific code here
void MoveControl(const CWnd* pParent, CWnd& control, int xDelta, int yDelta);
#endif

// Heap tracing can be enabled for one or two PIDs (can be enabled when they are
// already running, one or more process names (most enable through registry
// prior to process launch), or for a single process which xperf launches.
// These are all useful in different scenarios.
// In the context of Chrome profiling:
// Profiling a single process (of any type) that is already running can be done
// by specifying the PID of that process or pair of processes.
// Profiling all Chrome processes can be done by specifying chrome.exe. If this
// specification is done after launching the browser process then this will only
// heap-profile subsequently launched processes, so mostly newly created renderer
// processes, potentially from startup.
// Profiling the *browser* process from startup can be done by specifying the
// the full patch to the executable, and xperf will then launch this process and
// start heap tracing. Note that this will run Chrome as admin, so be careful.
// These options cannot be used together.

// The heap tracing options can be specified as a semi-colon separated list of
// process IDs (maximum of two) and process names *or* a single fully-qualified
// path name.
struct HeapTracedProcesses
{
// Space-separated set of process IDs (as required by -Pids), or an empty string.
std::wstring processIDs;
// Vector of process names, including .exe but not any path information.
std::vector<std::wstring> processNames;
// A single fully qualified executable which xperf will launch when tracing
// starts.
std::wstring pathName;
};

// Parse the semi-colon separated heap trace settings
HeapTracedProcesses ParseHeapTracingSettings(std::wstring heapTracingExes);

0 comments on commit 5618e5d

Please sign in to comment.