PerfView: How to .NET Core Framework Symbols and Source (On Windows)

In a previous blog on using PerfView with .NET Core  I pointed out that free PerfView profiling tool (see PerfView tutorial for more)  should 'just work' to collect performance data for .NET Core scenarios on windows operating system.    (On Linux,  only CPU analysis can be done current see CPU Analysis on Linux using PerfView for more).

However as I mentioned in that blog near the end, if you need to see what is going on inside the .NET Framework itself (and often this is very useful), you are not likely to get the symbols you need.     We have fixes in the pipeline, but it may be several more months (I gave the estimate of March 2017) until things get ironed out.

Thus if you collected a trace with PerfView for a .NET Core scenario and you don't get symbols for DLLs like System.* or Microsoft.*, this is likely the problem.    I wanted to reiterate the work around since it is not hard, but also not obvious.   If you collect the data by

  •      Opening an administrative command prompt (this is important, as the special options get lost if PerfView itself has to elevate)
  • Run PerfView /ForceNGENRundown /BufferSizeMB=256

Then collect as normal (using Collect -> Collect and hit the 'Start collection' button), should fix the problem.

Getting Framework Source Code

PerfView has a 'Goto Source' option (select a frame -> right click -> Goto Source), which can be made to work with the framework (as well as your own code).   We want this to 'just work' but again, we are not quite there yet.    If you try this, it is likely to fail, and you can see exactly why by looking in the PerfView log (button in lower right corner).    The typical failures is that it cant find the symbol file (PDB file), or it can't find the source file.   For your own code, you have to make sure that your symbol path includes directories where you built your symbol files.  You can add these by using the File -> Set Symbol Path.   For framework DLLs, the defaults should be fine (which look things up on a Microsoft-standard symbol server).

However the next step is that it needs to find the exact source file.   Again for your own code, you need to set a 'source path' that includes a base directory that is a parent of where your source code lives.   You can add these search locations by using File -> Set Source Path.    Again you can check the log to see exactly where PerfView looks and whether it finds the file or not.

For framework code, the Framework symbol files actually do have the information needed to find the source code, but there are permissions  problems currently.    The work-around currently is for you to download the source code yourself, and then point to it (as if it were your own code).

The .NET Framework code lives in one of two GitHub repositories.

  1.  which contains the runtime (mostly coreclr.dll, and corejit.dll) and that minimal part of the class library that is deeply connected to the runtime (called System.Private.Corelib.dll).
  2. which contains the rest of the .NET Core framework (thinks like System.XML* System.Collections.*  This is most of the public surface area of the .NET Runtime.

For this blog I am going to assume you know how to clone a hithub repo, if not take a look at these instructions for doing it using Visual Studio.

Once you have cloned these two repositories you need to set your source path in PerfView to include directory for each of these clones.   Finally, you need to synchronize both of the repositories to the exact state that corresponds to the version of the binaries you are actually using.

The key piece of information I want to provide here is to note that DLLs have in their version information the GitHub commit hash (ID) that allows you to fetch exactly the correct code.   Thus the basic procedure is

  1. Find the complete path for the .NET Runtime or framework DLL you need.
  2. Look in the version information for that DLL to get the GitHub commit ID. (and determine if the DLL was part of coreclr or the corefx repo)
  3. Sync the local clone of that code to that commit ID

At that point, assuming you have put these repos in your PerfView source path, PerfView will then look up exactly the correct source.

There are numerous different ways of achieving each of the steps above, but I will give you one way here.

  • To find the complete path for the DLL, open the 'Processes' view under the ETL.ZIP file and click on the 'View Process Modules in Excel' link.  This shows for every process exactly which DLL was loaded.
  • Once you have the path, open its directory in Windows explorer select the file right click -> Properties and select the 'Details' path.  You should see a line labeled 'Product version'  and it should say something like 'Commit Hash: 9688ddbb...'   This is what you are looking for.  You only need the first 8 bytes of that hash (The hash is actually very long but Git accepts any prefix that is unique in the repo, and 8 digits is almost certainly unique).   If the DLL coreclr, corejit or System.Private.Corelib.* then it lives in coreclr, otherwise it lives in corefx.
  • Once you have the commit ID you can go to the appropriate repository and sync to it.  One way of doing that is the following which created a name (branch) for the commit (in this case called Version1.1), and then uses git checkout to sync to it.
    • git branch Version1.1 9688ddbb
    • git checkout Version1.1

Tada!  At this point you should have exactly the right source code.    Now clearly this is a bit of a pain and we believe in a few months PerfView will 'just work' and automatically open the right source code for you, but in the mean time this is a way of working around it.