From 5618e5d8437f1ac703e345300e2077f09dfaa494 Mon Sep 17 00:00:00 2001 From: Bruce Dawson Date: Tue, 2 Oct 2018 18:33:15 -0700 Subject: [PATCH] Enhanced heap tracing abilities 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. --- UIforETW/Settings.cpp | 11 +++--- UIforETW/UIforETWDlg.cpp | 77 +++++++++++++++++++++++++++++++++++----- UIforETW/Utility.cpp | 55 ++++++++++++++++++++++++++++ UIforETW/Utility.h | 33 +++++++++++++++++ 4 files changed, 164 insertions(+), 12 deletions(-) diff --git a/UIforETW/Settings.cpp b/UIforETW/Settings.cpp index ff01ba08..2c7b00bb 100644 --- a/UIforETW/Settings.cpp +++ b/UIforETW/Settings.cpp @@ -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."); diff --git a/UIforETW/UIforETWDlg.cpp b/UIforETW/UIforETWDlg.cpp index 3493c4ab..e987e86d 100644 --- a/UIforETW/UIforETWDlg.cpp +++ b/UIforETW/UIforETWDlg.cpp @@ -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); @@ -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 @@ -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; } } @@ -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 @@ -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; @@ -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); diff --git a/UIforETW/Utility.cpp b/UIforETW/Utility.cpp index 22040eb3..e6dc54b8 100644 --- a/UIforETW/Utility.cpp +++ b/UIforETW/Utility.cpp @@ -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; +} diff --git a/UIforETW/Utility.h b/UIforETW/Utility.h index 162e475c..1ecad726 100644 --- a/UIforETW/Utility.h +++ b/UIforETW/Utility.h @@ -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 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);