Process Tree from an Xperf Trace

I was looking at an xperf (ETW) trace recently and needed to know who had started a particular process. The parent process ID was stored in the trace so I could find its parent, and its parent’s parent, and so on, but doing this for many generations in WPA is tedious.

Luckily the 8.1 version of the Windows Performance Toolkit has a tool for exporting arbitrary data, so I used that and a bit of Python to write a tool that would create a process tree for an ETW trace.

The process export process

imageThe process of exporting process data starts with configuring WPA so that it displays the process data that I want to export. To do this I closed all of the open WPA graphs and tables and opened the Processes graph, found under System Activity in the graph explorer – see the screenshot at the right. I put the Processes graph into Table Only mode (Ctrl+Shift+T) because the graph isn’t relevant for exporting.

Then it was just a matter of selecting and rearranging the columns that I wanted into the order that I wanted them. It made sense to have Process ID and Parent Process ID first, so that my script could easily find them. The remaining columns I treat as data payload and they can be rearranged any way you want. I ended up with something like this:

image

You can export this profile from WPA using the Export command in the Profile menu. It’s kind of obvious when you say it like that. You can then apply the profile to a loaded trace using the Apply command in the Profile menu, but we’re going to use the profile for exporting data.

I saved my profile as XperfProcessPercentage.wpaProfile.

Export made easy

Exporting the data is as simple as running this command:

wpaexporter  mytrace.etl  XperfProcessParentage.wpaProfile

This creates a .csv file containing the same data that was visible in the table. The only peculiar thing is the name of the .csv file, which cannot be directly specified. Not a big deal – just run the export process and look for the .csv file. In this case it will be Processes_Summary_Table_ProcessParentage.csv.

The .csv file name is derived from the table name and the view preset name. You can change the view preset name by going to the View Editor (Ctrl+E), then clicking Manage…

The rest is straightforward. I wrote the simplest possible .csv parsing code. It builds up two Python dictionaries – one that records the parent process ID for each process ID, and another which records additional data for each process ID. Then I loop through the processes. For each process the script finds the oldest ancestor, and then prints a tree starting from there. It avoids duplicate work by marking those processes which it has printed.

The result is something like this:

0, Idle,<Unknown>
    4, System,<Unknown>
        332, smss.exe,\SystemRoot\System32\smss.exe
408 (missing process),
    620, wininit.exe,wininit.exe
        696, services.exe,C:\Windows\system32\services.exe
            512, svchost.exe,C:\Windows\System32\svchost.exe -k Local…
                7272, audiodg.exe,C:\Windows\system32\AUDIODG.EXE 0xbd8
            1540, p4s.exe,”C:\Program Files\Perforce\Server\p4s.exe”
            1968, svchost.exe,C:\Windows\system32\svchost.exe -k Local…
            5044, OSPPSVC.EXE,”C:\Program Files\Common Files\Microsoft…
            1108, svchost.exe,C:\Windows\system32\svchost.exe -k netsvcs
                7264, wuauclt.exe,”C:\Windows\system32\wuauclt.exe”
            3160, svchost.exe,C:\Windows\System32\svchost.exe -k Local…
            632, svchost.exe,C:\Windows\System32\svchost.exe -k Local…
                2752, dwm.exe,”C:\Windows\system32\Dwm.exe”

Unlike a normal process tree this covers a range of time – the length of the trace. This means that not all of these processes necessarily lived at the same time. It would be trivial to add their lifetime data to the payload. It is possible that a process ID could be reused during the time period covered – in which case one of the processes would be ignored by my script. This is rare enough that I just check for it and print a message.

The process tree can be viewed quite effectively using a text editor, but if you want a proper tree view then just load the results into TabView.

Luke, I am your father (and also your son)

The process tree in Windows is a perfect tree, with one root that is the ancestor of all other processes. But this perfect tree is not present in the data – and things can get weird.

It is quite possible for a process to create another process, and then die. Now the child process has no living parent. Later on another process can be created that reuses the parent process ID, so now the child has an incorrect parent. And, there is no reason that this fake parent can’t be a descendant of the child, thus giving a loop.

This happened in the very first trace that I used as a test case. I thought my code was buggy, but I was just hitting unexpected data. Now my script checks for loops and when it detects one it stops and prints from there. I could try to improve this by selecting the oldest process in the loop as the root, but since most processes will have a start time of 0.0 (meaning the beginning of the trace) this is unlikely to help.

Alternatives

As Robin Giese pointed out in the comments you can also use an xperf processing action to dump the process tree:

xperf -i foo.etl -a process –tree

There are many available xperf processing actions, some of which do quite sophisticated processing, and if one of them meets your needs you should use it. To get a list of all of the available processing actions and help on the process action just run these two commands:

xperf -help processing
xperf -help processing process

The advantage of wpaexporter, however, is lets you export any data that you want, rather than being limited to the sets of data that the WPT developers support.

That is all

The Python script and .wpaprofile file both ship with UIforETW. You can download the latest release or find XperfProcessParentage.py and XperfProcessParentage.wpaProfile in the UIforETW bin directory.

This is one of my shortest posts. There is nothing more to say.

About brucedawson

I'm a programmer, working for Google, focusing on optimization and reliability. Nothing's more fun than making code run 10x as fast. Unless it's eliminating large numbers of bugs. I also unicycle. And play (ice) hockey. And sled hockey. And juggle. And worry about whether this blog should have been called randomutf-8. 2010s in review tells more: https://twitter.com/BruceDawson0xB/status/1212101533015298048
This entry was posted in xperf and tagged , , , , . Bookmark the permalink.

11 Responses to Process Tree from an Xperf Trace

  1. Robin Giese says:

    Also see xperf -i foo.etl -a process -tree. 🙂 (Though the Exporter is generally the way to go.)
    PIDs get reused pretty aggressively so it’s definitely a good idea to check for lifetime overlap. The treeing code in -a process is pretty careful on that front.

    • brucedawson says:

      Cool! Thanks Robin.

      The general advantage of wpaexporter is, of course, that it can export *any* data that wpa can view, in any format that is desired. I like, for instance, that my process tree contains the commandline, and that other data could easily be added.

      • brucedawson says:

        Unfortunately the lifetime overlap checking code in “-a process” is too conservative. Many processes live for the duration of the trace and “-a process” handles this poorly. If a parent process lives for the lifetime of the trace then it is listed twice. The first time it is listed as UNKNOWN (PID) and has listed as children all of the children who also lived the entire length of the trace.

        Separately it is listed with a process name and PID and there it has listed as children all of the children who only lived for part of the trace.

        This is true, but not very helpful. True, those other processes might have a different parent but I’d like an option to assume that they have the obvious parent, to make the data easier to understand. The “-a process” results end up with so many UNKNOWN processes that it is almost useless. I found that my own ‘unreliable’ parenting tree script was more useful, despite being potentially incorrect.

  2. Severin Pappadeux says:

    By the way, there is update to WPT in WDK 8.1 Update 1

    • brucedawson says:

      I just installed that but didn’t notice any difference. WPA still reports itself as being version 6.3.9600.16384 (winblue_rtm.130821-1623). This is, IIRC, the version from the Windows 8.1 SDK.

      • Severin Pappadeux says:

        I’m sorry, not in WDK, but in ADK, http://www.microsoft.com/en-us/download/details.aspx?id=39982

        There appear to be WPT msi as well as additional patches directory, marked 8.100.26629, with pathches WPTx64-x86…msp, WPTx86-x86…msp etc

        • brucedawson says:

          The WPT .msi files do appear to be changed, but the binaries installed to C:\Program Files (x86)\Windows Kits\8.1\Windows Performance Toolkit are unchanged — they still have the 8/20/2013 file date and the same version number shown above.

          If it’s a new WPT version then it is very cleverly disguised. It’s a pity since the 8.1 version has some bugs, such as in the display of VirtualAlloc call stacks.

  3. Pingback: Slow Symbol Loading in Microsoft’s Profiler, Take Two | Random ASCII

  4. Pingback: UIforETW – Windows Performance Made Easier | Random ASCII

  5. Pingback: ETW Central | Random ASCII

  6. Pingback: Exporting Arbitrary Data from xperf ETL files | Random ASCII

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.