dotTrace 2016.2 Help

Use Profiling API

In this tutorial, we'll learn how to use the profiling API in two main scenarios:

  • Profiling a specific part of the code.
  • Self-profiling of applications.

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.

  • 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 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:

  1. Make sure you have Visual Studio and dotTrace installed on your computer.
  2. Download the sample application from github.
  3. 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.

    t4_unblock

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 the Cell class).
  • Each 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()).

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.

  1. Open the GameOfLife solution in Visual Studio.
  2. To use the Profiling API, you must reference the JetBrains.Profiler.Windows.Api assembly which contains the API classes. To do this:
    • In Solution Explorer, right click References.
    • In the context menu, select Add Reference.
      t4_add_reference
    • In the opened window, click Browse | Browse and specify the path to JetBrains.Profiler.Windows.Api.dll located in the main Profiling SDK directory.
      t4_api_reference
    • Click OK.
      t4_api_reference_2
  3. 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 API code.
    private void OnTimer(object sender, EventArgs e) { _mainGrid.Update(); _genCounter++; lblGenCount.Content = "Generations: " + genCounter; }
    To control the profiling process, the API uses the static PerformanceProfiler class. To start profiling, you should first call the Begin method (creates a blank snapshot) and the Start method which actually starts measurements. Stop suspends measurements, while EndSave initiates snapshot processing in dotTrace. Let's try out these methods.
  4. 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 PerformanceProfiler.Begin and PerformanceProfiler.Start) and take a snapshot after the code is executed (with ProfilingControl.Stop and PerformanceProfiler.EndSave). After the correction, the code should look like follows:
    if (_genCounter == 1 && PerformanceProfiler.IsActive) { PerformanceProfiler.Begin(); PerformanceProfiler.Start(); } _mainGrid.Update(); _genCounter++; lblGenCount.Content = "Generations: " + genCounter; if (_genCounter == 2) { PerformanceProfiler.Stop(); PerformanceProfiler.EndSave(); }
    The genCounter field (generation counter) helps us to start and stop the profiler just once (to profile only a single _mainGrid.Update() execution). The PerformanceProfiler.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.
  5. In Visual Studio, select the menu ReSharper | Profile | Run Startup Configuration Performance Profiling.... This will open the profiler configuration window.
  6. Change the Profiling type to Tracing*.
  7. Select Advanced to see more profiling options.
  8. 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.
    t4_profiling_options
  9. Click Run.
  10. This will run the Game of Life application and open the profiling controller window.
    t4_app
    You might notice that the controller window doesn't have any controls. Profiling is now controlled only by the Profiling API.
  11. 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.
    t4_dT_threads
  12. In Performance Viewer, expand the Main thread node.
    t4_dT_threads_details
    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 the Attach 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.

  1. Open our Game of Life application in Visual Studio.
  2. To use the dotTrace API, you must reference the JetBrains.Profiler.Windows.SelfApi assembly which contains the API classes. To do this:
    • In Solution Explorer, right click References.
    • 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 Profiling SDK directory.
    • Click OK.
      t4_self_api_reference
  3. First, we need to initiate self-profiling using the SelfAttach class. This can be done anywhere in the application before we call PerformanceProfiler's methods. In our case, this could be done in the constructor of the main window. To do this, open the MainWindow() constructor in MainWindow.xaml.cs and add the following lines to the end of the constructor:
    SelfAttach.Attach(new SaveSnapshotProfilingConfig() { ProfilingControlKind = ProfilingControlKind.Api, SaveDir = "C:\\Temp", RedistDir = "C:\\ProfilerSDK", ProfilingType = ProfilingType.Performance, SnapshotFormat = SnapshotFormat.Uncompressed, ListFile = "C:\\snapshot_list.xml" });
    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 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 the SaveSnapshotProfilingConfig class allow us to specify the following profiling options*:
    • 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 с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 specify ProfilingType.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 value Compressed 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.
  4. 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:
    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(); }
  5. Build and run the application.
  6. Start Game of Life using the Start button. Wait until 10 generations pass.
  7. Check the folder you've specified as SaveDir. If everything works fine, it should contain the snapshot file.
    t4_self_api_snapshot
    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.

Last modified: 15 December 2016