Using the JetBrains.Profiler.Api package allows fine-grained control over when performance metrics are recorded. However, developers frequently encounter an issue where programmatic snapshots never appear in JetBrains dotTrace when executing code via CLI commands like dotnet watch run or custom launch profiles. If your calls to MeasureProfiler.SaveData() silently fail to produce snapshot files, here is why it happens and how to resolve it.

The Core Problem: Process Attachment & Environment Variables

dotTrace relies on specific CLR environment variables (such as COR_ENABLE_PROFILING or CORECLR_ENABLE_PROFILING) injected into the target process to establish communication between your code and the profiling engine.

When you start an application using:

dotnet watch run --launch-profile https

two common issues break profiler integration:

  • Child Process Decoupling: dotnet watch acts as a wrapper/parent process. The profiler attaches to the watcher process rather than the child worker process hosting your actual application code where MeasureProfiler is executed.
  • Missing Profiling Handshake: If you start dotTrace before the application without properly linking the session via dotTrace's launcher (or without inheriting the profiler's environment variables), the MeasureProfiler.StartCollectingData() and SaveData() calls become no-ops.

Step-by-Step Fix

1. Profile Without the Watcher

Avoid launching via dotnet watch during active profiling runs. Instead, attach dotTrace directly to the compiled executable or run standard dotnet run directly through dotTrace:

  • Open dotTrace and choose .NET Core Application (or Standalone Applications).
  • Set the Path to your project's built binary (your-app.dll or executable) or specify dotnet as the executable with arguments: run --no-build --launch-profile https.
  • Ensure the Profiling Type is set to Tracing.
  • Under Profiler API controls, verify that API control is enabled.
  • Start the session from dotTrace.

2. Proper Usage of the Profiler API

Ensure that you manage data collection state correctly. Wrapping the invocation in a try...finally block ensures snapshots are reliably flushed even if the method throws an exception:

using JetBrains.Profiler.Api; public async Task ReadWrite() { MeasureProfiler.StartCollectingData(); try { IDataDto data = await Read(); } finally { MeasureProfiler.SaveData(); // Flushes the collected data into a snapshot } await Write(data); }

3. Check the Snapshot Directory

By default, if no custom snapshot path is specified in dotTrace preferences, snapshots are saved into your local temporary folder: %USERPROFILE%\AppData\Local\Temp\JetBrains\SnapshotTemp. Alternatively, you can explicitly configure the snapshot destination within the dotTrace Run window before starting.

How to Measure Average Execution Time

Once your snapshot successfully loads into dotTrace, follow these steps to inspect the average execution time of your profiled method:

  • Open the snapshot in dotTrace Viewer.
  • Navigate to the Threads view and select your main thread or thread pool worker.
  • Switch to the Methods tab (or open the Call Tree).
  • Search for ReadWrite or Read.
  • Locate the Total Time, Own Time, and Call Count columns. dotTrace automatically calculates the Average Time (Total Time divided by Calls). If the column is hidden, right-click the table header and check Avg Time.

By bypassing process wrappers like dotnet watch and launching the target process directly via dotTrace, the profiler API will reliably capture and persist your designated execution slices.