Using the Windows Performance Toolkit to find memory leaks

The Windows Performance Toolkit can be used to monitor Heap activity, including monitoring the stacks of heap allocations. You can find out more about the Windows Performance Toolkit here:

http://msdn.microsoft.com/en-us/performance/default.aspx

http://msdn.microsoft.com/en-us/library/ff191077(v=VS.85).aspx

It is downloaded via the SDK installer that comes with the Windows 7 SDK. To install, simply start the SDK installer, and select the Windows Performance Toolkit option as shown here:

SNAGHTML12405cf

Its default install location, for a 64 bit system is: C:Program FilesMicrosoft Windows Performance Toolkit.

I will not explain how the Windows Performance Toolkit captures, processes or stores heap allocation data. The pages on MSDN explain that pretty thoroughly. What I want to explain here is a quick and simple way to capture heap information, and find a memory leak, with a basic stack trace. The main tool I use for this is called XPerf, short for XPerf.exe.

Preparation

First I want to demonstrate this by writing some simple code that will leak some memory. I included a few functions that get called from main, so as to display a non-trivial stack trace.

int* AllocSmallAmount(int val)
{
    return new int(val);
}
void RunForLoop(int max)
{
    printf("Test Leak: Start\n");
    for (int i = 0; i < max; i++)
    {
        int* ptr = AllocSmallAmount(i);
        printf("i: %d\n", i);
        Sleep(70);
    }
    printf("Test Leak: End\n");
}

int _tmain(int argc, _TCHAR* argv[])
{
    RunForLoop(10);
    return 0;
}

Note, that  works for both debug and release builds. But be sure that the compiler settings have ‘omit frame pointers’ turned off. This tool needs it off in order to provide correct stack traces when a heap event occurs.

So with that, I compile the code, and navigate to the directory where my binary (test_leak.exe) resides. I then created a batch script (which I call xperf_run.bat) that contains the following commands:

Code Snippet
  1. @echo off
  2. rem my binary that I want to test leaks for
  3. set bin=%CD%\test_leak.exe
  4. rem store the results here
  5. set savefile=%CD%\test_leak.etl
  6.  
  7. pushd "C:\Program Files\Microsoft Windows Performance Toolkit"
  8.  
  9. rem start the session
  10. xperf -on Base -BufferSize 512 -MinBuffers 5 -MaxBuffers 5
  11. xperf -start HeapSession -heap -PidNewProcess "%bin%" -WaitForNewProcess -BufferSize 512 -MinBuffers 5 -MaxBuffers 5 -stackwalk HeapAlloc+HeapRealloc+HeapFree
  12.  
  13. rem stop the session
  14. xperf -stop HeapSession -stop -d "%savefile%"
  15.  
  16. popd
  17. pause

Notice first, I set the path to my application I want to test, and I set the output path for where I want to store the results (line 5). Line 11 contains the code that will actually launch my app when I start this batch script. The parameter –PidNewProcess is followed by the full file path of application that will get launched by xperf. The parameter –WaitForNewProcess means that xperf will wait until my test application is done, before returning control to the batch script.

The first time you run this, you will get a warning from xperf:

xperf: warning: This system is not fully configured for x64 stack tracing.
Please modify the registry under:

  HKLMSystemCurrentControlSetControlSession ManagerMemory Management

and set the value:

  DisablePagingExecutive (REG_DWORD) = 1

Then reboot before retrying tracing.

Note: Tracing has been enabled, this is just a warning.

Which means you have to modify the registry value, and reboot your computer. Once you do that, you can continue to run the application. Which gives the following results:

Test Leak: Start
i: 0
i: 1
i: 2
i: 3
i: 4
i: 5
i: 6
i: 7
i: 8
i: 9
Test Leak: End
Merged Etl: C:Users<user name>DocumentsVisual Studio 2010Projectstest_leakReleasetest_leak.etl
The trace you have just captured "C:Users<user name>DocumentsVisual Studio 2010Projectstest_leakReleasetest_leak.etl" may contain personally identifiable information,
o files accessed, paths to registry accessed and process names. Exact information depends on the events that were logged. Please be aware of this when sharing out this trac
Press any key to continue . . .

So that is it. The rest is the hard part: deciphering the results.

Analyzing the results

Double click the file test_leak.etl, and select yes to any warning dialog popup’s that may appear. Will will show you two progress bars, and then you will see a window with a lot of graphs or charts in it.

image

There are actually quite a few charts to look at here. With possibilities of adding many more if you choose different command line parameters. For the purposes of a memory however, we are interested in two things:

  1. Outstanding heap allocation size
  2. Outstanding heap allocation count

So I like to filter out everything except those two. Click on the side bar to expand the FrameList, and uncheck everything except Heap Outstanding Allocation Size and Heap Outstanding Allocation Count;

image

Next, take the splitter bar under the graph for Heap Outstanding Allocation Count and drag it down. You might have to expand the window size to make it fit. Doing this, you can see the stair step effect of a heap slowly growing in regular, even increments. That is the heap that we are interested in.

image

It is obvious that the other two heaps (There are three total) are not allocating or freeing memory during the majority of the process lifetime, but rather maintain a steady state. The heap in green is the one we are interested in. You can turn on or off the display of any heap by clicking on the legend, and checking or unchecking various heaps. As the following picture shows.

image

Summary Table

To examine the stack trace of the memory leaks, you first select Load Symbols from the Trace menu:

image

Then you right click over the graph and select Summary Table.

image

This summary table is a listview that contains records and fields of data. By default stack traces are turned off. Choosing what data to display is similar to choosing data to display for the graphs. You open the tool panel on the left, and check Stack from the available checkboxes. In this example I am interested in looking at: Type, HeapHandle, Process, Stack, Count and Size. I then drag the Type column over to the far left, and then place HeapHandle to the right that.

If I left the selection blank when I right clicked, and selected summary table, I would get a graph that included the entire lifetime of the process, from start to finish. If you did that all memory allocations were allocated and freed inside your ‘selected’ timeblock, which in this case is the entire length of time that the Kernel was logging heap events for this application. In this case, the x axis on the graph shows about 1.95 seconds, of which all memory allocations took place between about 0.25 seconds and were freed about 0.95 seconds. The windows performance analyzer calls this Allocated Inside / Freed Inside or AIFI for short. If you look at the summary table, you will see a type called AIFI:

image

Expand it, and you will see that there were five heaps active during the lifetime of this process:

image

You will also see some stacks associated with each heap, with the words [Root] next to it.

Here open up the one of the stack traces, starting at root until you find a call to __tmainCRTStartup.

image

This is the heap that all the memory was leaked on. There underneath __tmainCRTStartup is a call to wmain (assuming you compiled in unicode). expand that to drill down into the callstacks that came out of main, until you find the memory leak:

image

There you can see the call stack for the memory that leaked 10 times for a total of 40 bytes of memory over the course of the applications lifetime.

Conclusion

And that brings me to the conclusion that this tool is worthless for finding memory leaks for any app larger than the directions on a shampoo bottle. This memory leak is completely buried with all sorts of other memory data. As you can see, it is mixed in the middle of 5 process heaps. Getting to it involved drilling down deep into code that you had to previously be familiar with. It is not at all obvious that this is a memory leak, unless you knew it was there in the first place. Which completely defeats the purpose of using this tool. Since you want a tool to find memory leaks in places where you do not know where they are.

There may be other tools for finding memory leaks, but this is not one of them.

Advertisements