In this tutorial, we’ll learn how to use the dotTrace Performance API in two main scenarios:
- Profiling a specific part of the code.
- Self-profiling of applications.
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. For additional information about the API, please refer to the dotTrace SDK Help supplied with the SDK.
Before going any further, let’s take a little bit more detailed look at the API usage scenarios.
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.
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.
* Must be supplied with your app.
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!
Before we continue, please accomplish the following prerequisites:
- Make sure you have Visual Studio 2010 or later and dotTrace Performance 5.x installed on your computer.
- Download and extract the archive with the sample app.
- Download and install dotTrace Performance SDK.
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. 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 the Cell class).
- The next generation of cells is created once in 200 ms: The OnTimer event handler of the DispatcherTimer instance runs the Grid.Update() method which calculates cell states for the next generation (Grid.UpdateToNextGeneration()) and updates graphics (Grid.UpdateGraphics()).
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.dotTrace.ProfilingApi 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.dotTrace.ProfilingApi.dll located in the Lib directory of the dotTrace SDK. By default, this is C:\Program Files (x86)\JetBrains\dotTrace\Profiler SDK v5\Lib.
- Click OK.
- Open the OnTimer method of the MainWindow class. As you see, it is the timer’s event handler which updates the Grid instance by running the mainGrid.Update method. This makes the OnTimer event handler a perfect candidate for injecting our dotTrace API code.
To control the profiling process, the API uses the static ProfilingControl class. Profiling is not started initially, therefore Start or StartPaused should be called first when profiling is needed. Stop initiates snapshot generation and processing. Cancel drops collected data and puts profiling into stopped state. In addition, there are Pause and Resume methods that allow you to temporary disable profiling data collection without stopping the profiler and generating a snapshot.
Thus, all we need is to start profiling with Start and take a snapshot using Stop. Let’s do this in the code.
- 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 of OnTimer (with ProfilingControl.Start) and take a snapshot after the code is executed (with ProfilingControl.Stop). 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 single mainGrid.Update() execution). The ProfilingControl.IsActive is used to determine whether the API is enabled.
After the changes are made, let’s run the profiling.
- In Visual Studio, select the menu DOTTRACE | Profile Startup Project.
This will open the Profiler Configuration window used to configure the profiling session.
* The Tracing type is much better suited for our case. The thing is in the Sampling mode, dotTrace takes call stack information (samples) once in a period of time from 5 to 11 ms. As a single execution of the Update() method probably takes less time, the chances it won’t be detected by the profiler are quite high. In the tracing mode, dotTrace gets information about every function entry and exit right from CLR. That’s why this method gives more detailed results.
- Change the Profiling type to Tracing*.
- Click the button 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 dotTrace Performance Controller window.
As you can see in the Controller window, all buttons used to control the profiling session (excepting Kill Process) 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 the main window.
- In dotTrace Performance, open the Threads Tree view (the button on the left pane).
As you see, the thread view contains information only about a single call of OnTimer. Just what we needed!
Now, let’s move to 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:
*Our Game of Life application already includes redistributables in the Redist folder.
- End-users don’t have dotTrace installed; therefore, you must include dotTrace redistributables* 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. Calling the Attach method prepares profiling configuration and executes the profiler from your “redistributables” folder.
**The limitation is related with the .NET internal architecture.
- 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.
- First, we need to initiate self-profiling using the SelfAttach class. This can be done anywhere in the app before we call the ProfilingControl.Start method. In our case, this could be done in the constructor of the main window. To do this, open the public 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 the SaveSnapshotProfilingConfig class as a parameter to the Attach 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 are also two more options you can use instead:
- OpenProfilingConfig will try to open the snapshot in dotTrace (if it is installed, of course).
- ProcessSnapshotProfilingConfig 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.
* Most of these parameters are optional. For more information about them, refer to the dotTrace SDK Help supplied with the SDK.
Public fields of the SaveSnapshotProfilingConfig class allow us to specify the following profiling options*:
- #* SavePath (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. The ProfilingControlKind.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.
- TempPath specifies where to store temporary session files.
- RedistPath specifies the path to the dotTrace redistributables.
- SnapshotFormat defines the format of storing snapshots. It can be either compressed or uncompressed.
- 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 the OnTimer 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 10 OnTimer executions. To do this, in the OnTimer 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.
* The samples provided with the SDK (in the Samples folder) demonstrate more complete API usage implementation and also worth your look.
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.