Disclaimer: This tutorial covers the usage of API only for the performance profiling method. The implementation of the timeline profiling API is very similar, with a few minor exceptions. For details on timeline profiling API classes, refer to the dotTrace documentation.
In this tutorial, we’ll learn how to use the dotTrace API in two main scenarios:
- Profiling a specific part of the code.
- Self-profiling of applications.
How the API Works
The dotTrace API is the main part of the dotTrace SDK. Without going into details, the API provides a number of classes which allow you to control the profiling process. For example, you can start and stop collecting profiling data, save collected snapshots and so on right from your app.
Before going any further, let’s take a little bit more detailed look at the API usage scenarios.
Profiling a Specific Part of the Code
Typically, if your app is quite huge, you don’t need to profile it entirely. The main point of your interest is the performance of some new functionality or a certain method. Using the dotTrace API, you can narrow the profiling scope. More specifically, the API allows you to start profiling measurements and collect snapshots in the exact places of your code.
Self-Profiled Applications
dotTrace API gives you a great opportunity to collect statistics about how your app behaves on end-user desktops. Do they face any performance issues and where is the “weakest” point in your app? Due to high PC hardware fragmentation, this information may be of great interest to you.
For this purpose, the API allows you to attach the dotTrace engine* to the running process in the background and take snapshots right from the code. Collected snapshots can be saved on the disk or passed to any other app (for example, a client that will send them to your server over HTTP). This is called self-profiling as the app, in some sense, controls the profiling by itself.
Now, let’s take some simple app and try both scenarios in action!
Contents
Prerequisites
Sample App
Profiling a Specific Part of the Code
Self-Profiled Application
Prerequisites
Before we continue, please accomplish the following prerequisites:
- Make sure you have Visual Studio 2010 or later and dotTrace 6 installed on your computer.
- Download and extract the archive with the sample app.
- Download and install dotTrace Performance SDK.
Sample App
For the example, we’ll use the classic Conway’s Game of Life application. If you’re not acquainted with the app, please read the wiki article (http://en.wikipedia.org/wiki/Conway's_Game_of_Life). This will ease the understanding of the tutorial.
Our WPF-based implementation of Game of Life works in the following way:
- A single instance of the
Grid
class is responsible for storing, updating, and displaying of a huge number of cells (instances of theCell
class). - The next generation of cells is created once in 200 ms: The
OnTimer
event handler of theDispatcherTimer
instance runs theGrid.Update()
method which calculates cell states for the next generation (Grid.UpdateToNextGeneration()
) and updates graphics (Grid.UpdateGraphics()
).
Profiling a Specific Part of the Code
Let’s assume we have made some changes to the way we calculate the next generation. Now, we want to check how the performance has changed. The dotTrace API will allow us to profile nothing but the method we want - Grid.Update()
. All we need is to start collecting profiling data right before the method is executed and collect a snapshot after the execution.
- Open the GameOfLife solution in Visual Studio.
- To use the dotTrace API, you must reference the
JetBrains.Profiler.Windows.Api
assembly which contains the API classes. To do this:- In the Solution Explorer, right click the References folder.
- In the context menu, select Add Reference.
- In the opened window, click Browse | Browse and specify the path to JetBrains.Profiler.Windows.Api.dll located in the main directory of the dotTrace SDK.
- Click OK.
Open the
OnTimer
method of theMainWindow
class. As you see, it is the timer’s event handler which updates theGrid
instance by running themainGrid.Update
method. This makes theOnTimer
event handler a perfect candidate for injecting our dotTrace API code.To control the profiling process, the API uses the static
PerformanceProfiler
class. To start profiling, you should first call theBegin
method (creates a blank snapshot) and theStart
method which actually starts measurements.Stop
suspends measurements, whileEndSave
initiates snapshot processing in dotTrace. Let’s try out these methods in our app.For example, we need to profile only a single execution of the
OnTimer
event handler. To do this, we should start profiling in the beginning ofOnTimer
(with PerformanceProfiler.Begin andPerformanceProfiler.Start
) and take a snapshot after the code is executed (withProfilingControl.Stop
and PerformanceProfiler.EndSave). After the correction, the code should look like follows:The
genCounter
field (generation counter) helps us to start and stop the profiler just once (to profile only a singlemainGrid.Update()
execution). ThePerformanceProfiler.IsActive
is used to determine whether the API is enabled. It must be checked only once.
After the changes are made, let’s run the profiling.- In Visual Studio, select the menu ReSharper | Profile | Run Startup Configuration Performance Profiling....
This will open the profiler configuration window used to configure the profiling session. -
Change the Profiling type to Tracing*.
- Select Advanced to see more profiling options.
- Check the Use profiler API option. This will make the API commands we added to the code work.
In the end, the profiler configuration window should look like follows.
- Click Run.
- This will run the Game of Life application and open the Controller window.
As you can see in the Controller window, buttons used to control the profiling session (excepting Start and Kill All) are disabled. Profiling is now controlled by the dotTrace API. - As the
OnTimer
event handler is executed only when Game of Life is started, start the game using the Start button.
Right after the second generation is generated, dotTrace will take a snapshot and open it in Performance Viewer.
- In Performance Viewer, expand the Main thread node.
As you see, the thread view contains information only about a single call of OnTimer. Just what we needed!
Self-Profiled Application
Now, let’s move to a more complex scenario of the self-profiled application. For example, we are concerned about app performance on end-users desktops and decide to collect such statistics. Here are some considerations you should know about before implementing self-profiling:
- End-users don’t have dotTrace installed; therefore, you must include SDK into your app’s installation package.
- As end-users are not supposed to initiate profiling, the self-profiling session is initiated by the API.
For this purpose, the API uses the static
SelfAttach
class located in the JetBrains.Profiler.Windows.SelfApi.dll library. Calling theAttach
method prepares profiling configuration and executes the profiler from the SDK folder.- As the profiler is attached to the already running process, there is a limitation* on the profiling type - it could be only Sampling.
Let’s try self-profiling in action.
- Open our Game of Life application in Visual Studio.
- To use the dotTrace API, you must reference the
JetBrains.Profiler.Windows.SelfApi
assembly which contains the API classes. To do this:- In the Solution Explorer, right click the References folder.
- In the context menu, select Add Reference.
- In the opened window, click Browse | Browse and specify the path to JetBrains.Profiler.Windows.SelfApi.dll located in the main directory of the dotTrace SDK.
- Click OK.
First, we need to initiate self-profiling using the
SelfAttach
class. This can be done anywhere in the app before we call thePerformanceProfiler
methods. In our case, this could be done in the constructor of the main window. To do this, open thepublic MainWindow()
constructor in MainWindow.xaml.cs and add the following lines in the end of the constructor:
As you see, we pass an instance of theSaveSnapshotProfilingConfig
class as a parameter to theAttach
method. This tells dotTrace how to process the resulting snapshot. In our case, we tell the profiler to save the snapshot file on the disk. There is also one more option you can use instead:ExecutableSnapshotProfilingConfig
will run an external application passing the path to the snapshot as a command-line parameter. This option is of most interest in case you’re going to get snapshots from end-user computers remotely.
Public fields of theSaveSnapshotProfilingConfig
class allow us to specify the following profiling options*:SaveDir
(passed to the constructor) specifies the location where we want to save snapshot files (../
in our case).ProfilingControlKind
defines how the profiling must be controlled. TheProfilingControlKind.API
value means we’ll control the session by means of the API. You can also decide to control the session using the Controller window or don’t control it at all.RedistDir
specifies the path to the dotTrace redistributables.ProfilingType
defines the profiling method. E.g., if you want to perform timeline profiling, you should specifyProfilingType.Timeline
here.ListFile
stores file names of snapshots collected during profiling. This file is created automatically during profiling.
As mentioned in the beginning, the only possible profiling type when using self-profiling is Sampling. Due to this, we cannot profile just a single execution of the
OnTimer
event handler as in the previous scenario. As theOnTimer
execution probably takes less than the sampling time (5 – 11 ms), the chances it won’t be detected by the profiler are quite high. Therefore, we need to extend the profiling scope and take performance results of, for example, first 10OnTimer
executions. To do this, in theOnTimer
event hanlder, we must say the profiler to stop and save snapshot only after the 10th Game of Life generation:- Build and run the app.
- Start Game of Life using the Start button. Wait until 10 generations pass.
- Check the app’s folder. If everything works fine, it should contain the snapshot file.
The resulting snapshot file can be then opened and inspected in dotTrace Performance Viewer.
Of course, the “real-life” self-profiling implementation may differ from the given example*. Thus, it would be great to handle main API exceptions, care for the cases when a user stops the game before the profiling is finished, and so on. Nevertheless, the provided example contains the minimum you should know to implement self-profiling in your app.