Use Profiling API
In this tutorial, we'll learn how to use the profiling API in two main scenarios:
How the API Works
The profiling API is the main part of the profiling 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 application.
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 application 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.
For this purpose, the API allows you to attach the profiling 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 application, in some sense, profiles itself.
Now, let's take some simple application and try both scenarios in action!
Prerequisites
Before we continue, please accomplish the following prerequisites:
- Make sure you have Visual Studio and dotTrace installed on your computer.
- Download the sample application from github.
- Download and install
Profiling SDK.
IMPORTANT! If your domain policy is to treat the files downloaded from the Internet as unsafe, unblock the zip archive using the Unblock button in file properties.
Sample Application
For the example, we'll use the classic Conway's Game of Life application. 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). -
Each 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 Profiling API allows 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 Profiling API, you must reference the
JetBrains.Profiler.Windows.Api
assembly which contains the API classes. To do this: -
Open the
OnTimer
method of theMainWindow
class. As you see, it is the timer's event handler which updates theGrid
instance by running the_mainGrid.Update()
method. This makes theOnTimer
event handler a perfect candidate for injecting our API code.To control the profiling process, the API uses the staticprivate void OnTimer(object sender, EventArgs e) { _mainGrid.Update(); _genCounter++; lblGenCount.Content = "Generations: " + genCounter; }
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. -
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
(withPerformanceProfiler.Begin
andPerformanceProfiler.Start
) and take a snapshot after the code is executed (withProfilingControl.Stop
andPerformanceProfiler.EndSave
). After the correction, the code should look like follows:Theif (_genCounter == 1 && PerformanceProfiler.IsActive) { PerformanceProfiler.Begin(); PerformanceProfiler.Start(); } _mainGrid.Update(); _genCounter++; lblGenCount.Content = "Generations: " + genCounter; if (_genCounter == 2) { PerformanceProfiler.Stop(); PerformanceProfiler.EndSave(); }
genCounter
field (generation counter) helps us to start and stop the profiler just once (to profile only a single_mainGrid.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 . This will open the profiler configuration window.
- Change the Profiling type to Tracing*.
- Select Advanced to see more profiling options.
- Select the Use profiler API option. This will make the API commands we added to the code work. The profiler configuration window should look like follows.
- Click Run.
- This will run the Game of Life application and open the profiling controller window. You might notice that the controller window doesn't have any controls. Profiling is now controlled only by the Profiling 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 cells 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 the
Update()
method. Just what we needed!
Self-Profiled Application
Now, let's move to a more complex scenario - a self-profiled application. For example, we are concerned about application 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: -
First, we need to initiate self-profiling using the
SelfAttach
class. This can be done anywhere in the application before we callPerformanceProfiler
's methods. In our case, this could be done in the constructor of the main window. To do this, open theMainWindow()
constructor in MainWindow.xaml.cs and add the following lines to the end of the constructor:As you see, we pass an instance of theSelfAttach.Attach(new SaveSnapshotProfilingConfig() { ProfilingControlKind = ProfilingControlKind.Api, SaveDir = "C:\\Temp", RedistDir = "C:\\ProfilerSDK", ProfilingType = ProfilingType.Performance, SnapshotFormat = SnapshotFormat.Uncompressed, ListFile = "C:\\snapshot_list.xml" });
SaveSnapshotProfilingConfig
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*:-
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 сontroller window or don't control it at all. -
SaveDir
specifies the location where we want to save snapshot files. - RedistDir specifies the path to Profiling SDK.
-
ProfilingType
defines the profiling method. E.g., if you want to perform timeline profiling, you should specifyProfilingType.Timeline
here. -
SnapshotFormat
defines the format of the snapshot files.Uncompressed
means that the snapshot will be saved as a number of uncompressed files which you can open in dotTrace without any additional actions. The default valueCompressed
tells the API to save snapshot files in a single zip which is more convenient for further distribution. -
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:while (SelfAttach.State != SelfApiState.Active) Thread.Sleep(250); if (_genCounter == 1 && PerformanceProfiler.IsActive) { PerformanceProfiler.Begin(); PerformanceProfiler.Start(); } _mainGrid.Update(); _genCounter++; lblGenCount.Content = $"Generations: {_genCounter}"; if (_genCounter == 10) { PerformanceProfiler.Stop(); PerformanceProfiler.EndSave(); }
- Build and run the application.
- Start Game of Life using the Start button. Wait until 10 generations pass.
- Check the folder you've specified as
SaveDir
. 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 application.