Modern apps tend to be multithreaded. Background data caching, background search, and so on: Even in a simple app you’ll definitely find a process that can be run in a separate thread. Unfortunately, in addition to obvious benefits, this also makes your app more complicated and more difficult for debugging.
What Is Timeline?
To help you profile multithreaded apps, dotTrace 6.0 introduces a new view called Timeline. Timeline is a set of filters and diagrams that allow you to quickly visualize and analyze threads and thread-to-thread relationships in your app. The purpose of Timeline is answering main questions related to poor performance of multithreaded apps:
- What causes user interface (UI) freezes in my app? Is this excessive computational work in the main thread, multiple garbage collections in worker threads, or maybe it’s an overuse of I/O operations?
- Is poor performance caused by a concurrency issue like lock contention, irregular work distribution, or other?
Timeline Is Not “Just a View”
Nevertheless, don’t think of Timeline as of just one more dotTrace view made “specifically for multithreading”. It is much, much more than that. In addition to capabilities described above, you can use Timeline as an ordinary profiler: dig into the call stack, view execution times and the source code of methods in the stack, and so on. Actually, you can do performance profiling in Timeline without the need in other views at all. Yep, Timeline is more a profiler in a profiler than just a view.
In this “Getting Started” tutorial we will take a detailed look at the Timeline user interface and even try to solve a very common task – finding a reason of UI freezes in an app.
As an example, we’ll use a small app used to reverse lines (e.g., "ABC" > "CBA") in text files. Briefly: With the Select Files button, a user specifies text files to be processed. The Process Files button runs a separate BackgroundWorker thread which reverses lines in the files. The progress of file processing is displayed in a label on the main window. After the processing is finished, the label shows the "All files were successfully processed" message.
The source code of the app is available here.
The app has a serious drawback. After starting file processing, users experience long UI lags that last until the processing is over.
Let’s use Timeline to determine the cause of these freezes!*
Running the Profiler and Getting a Snapshot
- Open the MassFileProcessing.sln solution in Visual Studio.
- In case you have dotTrace integrated with Visual Studio, run the profiler by choosing dotTrace | Profile Startup Project.
Otherwise, run dotTrace from the Windows Start menu. In dotTrace, select Profile | Standalone application.
- In Profiler Configuration, leave the Sampling profiling type. The thing is Timeline information can be gathered only in the sampling mode.
In case you ran dotTrace as a standalone app, you should also specify the path to the release executable of our sample app in Application.
- After you click Run, dotTrace runs our app and a special dotTrace 6.0 Controller window used to control the profiling process.
- Note that after you start profiling, dotTrace starts showing the real-time data about CPU usage and I/O operations in our app.
Now, we should reproduce a performance issue in our app.
- Click the Select Files button and choose five text files that come with the app in the Text Files folder.
- Click the Process Files button to start file processing.
As you can see, the app lags very badly. Actually, you are even unable to see the progress of file processing until it is finished and the All files were processed successfully message is shown.
- Collect a performance snapshot by clicking Get Snapshot in the dotTrace 6.0 Controller window. The collected snapshot will be opened in dotTrace.
- Close the app. This will also close the controller window.
- In dotTrace, switch to the Timeline view by clicking the Threads Timeline button.
First Look at the Timeline’s User Interface
Now, let’s make a little digression and take a look at the Timeline UI.
The concept of the Timeline UI is very simple:
- You have profiling data on input (see Source in (1) Overview).
- You apply a number of filters (2) to it. For example, you can ask Timeline to “Show all calls in Thread#1 that performed I/O operations in the timeframe between 2 s and 3 s” or “Show what thread blocked the Main thread”, and so on.
The list of currently applied filters is shown in (1) Overview.
- You get the filtered data on output.
What’s the most interesting about steps 2 and 3? The answer is - both steps are performed using the same UI controls. Each block (2) is used not only to select a specific filter but also to display a chunk of filtered information. This leads us to the other Timeline UI concept -- all filters affect each other. Thus, if you select System in Tread Types, Timeline will grey out all non-system threads in Threads Diagram, Call Tree will contain calls from the system threads only, the Tread States filter will contain only states that were detected for system threads, and so on.
For better understanding of this approach, let’s try the UI in action.