iOS / macOS

Below is a guide for profiling .NET iOS and .NET MacOS applications:

Profiling in .NET

See Profiling App Launch for information about how to profile the app's launch.

Note Only debug builds have profiling support enabled by default.

mlaunch

This document references the mlaunch tool, the easiest way to get it is to install Xamarin.iOS if you don't have it installed already. Latest package as of this writing: xamarin.ios-15.10.0.5.pkg.

Once installed, the mlaunch tool can be found here:

/Library/Frameworks/Xamarin.iOS.framework/Versions/Current/bin/mlaunch

The mlaunch tool is also installed with the iOS workload for .NET, but the location on disk depends on the actual version installed, so it's a bit harder to find.

In any case, creating an alias will allow the instructions below to work:

alias mlaunch=/Library/Frameworks/Xamarin.iOS.framework/Versions/Current/bin/mlaunch

Otherwise replace mlaunch with the full path according to the location on disk on your system.

Mobile apps

Initial configuration

We need the dotnet-dsrouter and dotnet-trace tools, so let's install them:

$ dotnet tool install --global dotnet-dsrouter
$ dotnet tool install --global dotnet-trace

Note This pull request for dotnet-dsrouter is required for profiling on device to work: https://github.com/dotnet/diagnostics/pull/3134

Simulator

  1. The first step is to launch the tool that provides a connection between the app and the .NET tracing tools:

    $ dotnet-dsrouter client-server -ipcc ~/my-sim-port -tcps 127.0.0.1:9000
  2. Launch the app and make it suspend upon launch (waiting for the .NET tooling to connect):

    $ mlaunch --launchsim bin/Debug/net*/*/*.app --device :v2:runtime=com.apple.CoreSimulator.SimRuntime.iOS-15-4,devicetype=com.CoreSimulator.SimDeviceType.iPhone-11 --wait-for-exit --stdout=$(tty) --stderr=$(tty) --argument --connection-mode --argument none '--setenv:DOTNET_DiagnosticPorts=127.0.0.1:9000,suspend'
  3. At this point it's necessary to wait until the following line shows up in the terminal:

    The runtime has been configured to pause during startup and is awaiting a Diagnostics IPC ResumeStartup command from a Diagnostic Port

  4. Once that's printed, go ahead and start profiling:

    $ dotnet-trace collect --diagnostic-port ~/my-sim-port --format speedscope

How to find a simulator to use

Execute the following to get the list of all available simulators on your machine:

$ xcrun simctl list devices
== Devices ==
-- iOS 12.4 --
    iPhone 6 (iOS 12.4) (C49E6049-5F51-40BB-833A-1B36C4EB95A6) (Booted)
-- iOS 16.0 --
    iPhone X (iOS 16.0) (50BCC90D-7E56-4AFB-89C5-3688BF345998) (Booted)
    iPhone SE (3rd generation) - iOS 16.0 (BE619DC5-F728-4E2F-9425-4731778A78FC) (Shutdown)
    iPad Air (5th generation) - iOS 16.0 (E6C465F0-43E5-4066-A2E9-D293254CBD30) (Shutdown)
-- tvOS 16.0 --
    Apple TV (tvOS 16.0) (ED020B77-8E3D-4BB7-B7D7-E94ECCCD62BE) (Shutdown)
-- watchOS 6.0 --
    Apple Watch Series 3 - 38mm (watchOS 6.0) (BB2AB52D-803B-42EA-A456-66ACBF90DF25) (Booted)
-- watchOS 9.0 --
    Apple Watch Series 7 - 41mm (watchOS 9.0) (2FAEA489-CD7F-42D9-9488-9B21858BB30F) (Booted)

Then pick an applicable device, and copy the UDID (50BCC90D-7E56-4AFB-89C5-3688BF345998 would be an example here), and pass the following to mlaunch:

$ mlaunch ... --device :v2:udid=50BCC90D-7E56-4AFB-89C5-3688BF345998 ...

It's also possible to specify a runtime/devicetype combination; the valid values can be queried with simctl like this:

$ xcrun simctl list devicetypes
== Device Types ==
iPhone 4s (com.apple.CoreSimulator.SimDeviceType.iPhone-4s)
iPhone 5 (com.apple.CoreSimulator.SimDeviceType.iPhone-5)
iPhone 5s (com.apple.CoreSimulator.SimDeviceType.iPhone-5s)
iPhone 6 Plus (com.apple.CoreSimulator.SimDeviceType.iPhone-6-Plus)
iPhone 6 (com.apple.CoreSimulator.SimDeviceType.iPhone-6)
iPhone 6s (com.apple.CoreSimulator.SimDeviceType.iPhone-6s)
iPhone 6s Plus (com.apple.CoreSimulator.SimDeviceType.iPhone-6s-Plus)
iPhone SE (1st generation) (com.apple.CoreSimulator.SimDeviceType.iPhone-SE)
iPhone 7 (com.apple.CoreSimulator.SimDeviceType.iPhone-7)
iPhone 7 Plus (com.apple.CoreSimulator.SimDeviceType.iPhone-7-Plus)
iPhone 8 (com.apple.CoreSimulator.SimDeviceType.iPhone-8)
iPhone 8 Plus (com.apple.CoreSimulator.SimDeviceType.iPhone-8-Plus)
iPhone X (com.apple.CoreSimulator.SimDeviceType.iPhone-X)
iPhone Xs (com.apple.CoreSimulator.SimDeviceType.iPhone-XS)
[...]

and

$ xcrun simctl list runtimes available
== Runtimes ==
iOS 12.4 (12.4 - 16G73) - com.apple.CoreSimulator.SimRuntime.iOS-12-4
iOS 13.5 (13.5 - 17F61) - com.apple.CoreSimulator.SimRuntime.iOS-13-5
iOS 15.4 (15.4 - 19E240) - com.apple.CoreSimulator.SimRuntime.iOS-15-4
tvOS 12.4 (12.4 - 16M567) - com.apple.CoreSimulator.SimRuntime.tvOS-12-4
tvOS 15.4 (15.4 - 19L439) - com.apple.CoreSimulator.SimRuntime.tvOS-15-4
tvOS 16.0 (16.0 - 20J5299n) - com.apple.CoreSimulator.SimRuntime.tvOS-16-0
tvOS 16.0 (16.0 - 20J5319f) - com.apple.CoreSimulator.SimRuntime.tvOS-16-0
watchOS 6.0 (6.0 - 17R575) - com.apple.CoreSimulator.SimRuntime.watchOS-6-0
watchOS 6.2 (6.2.1 - 17T531) - com.apple.CoreSimulator.SimRuntime.watchOS-6-2
watchOS 8.5 (8.5 - 19T241) - com.apple.CoreSimulator.SimRuntime.watchOS-8-5
watchOS 9.0 (9.0 - 20R5287q) - com.apple.CoreSimulator.SimRuntime.watchOS-9-0
watchOS 9.0 (9.0 - 20R5307f) - com.apple.CoreSimulator.SimRuntime.watchOS-9-0

and then finally pass the selected values to mlaunch like this:

$ mlaunch ... --device :v2:runtime=com.apple.CoreSimulator.SimRuntime.iOS-15-4,devicetype=com.CoreSimulator.SimDeviceType.iPhone-X ...

Device

Note This pull request for dotnet-dsrouter is required for profiling on device to work: https://github.com/dotnet/diagnostics/pull/3134

The first step is to connect an iOS device to the Mac using a USB cable.

The process is very similar to the process for the simulator:

  1. Launch the tool that bridges the app and the .NET tracing tools:

    $ dotnet-dsrouter server-client -ipcs ~/my-dev-port -tcpc 127.0.0.1:9001 --forward-port iOS

    Compared to the simulator, this:

    • Adds --forward-port iOS

    • Changes the local ports, both client (~/my-dev-port vs ~/my-sim-port) and server (:9001 vs :9000) - it's easier to debug any problems when using different ports.

  2. Install & launch the app and make it suspend upon launch:

    $ mlaunch --installdev bin/Debug/net*/*/*.app --devname ... 
    $ mlaunch --launchdev bin/Debug/net*/*/*.app --devname ... --wait-for-exit --argument --connection-mode --argument none '--setenv:DOTNET_DiagnosticPorts=127.0.0.1:9001,suspend,listen'
  3. At this point it's necessary to wait until the following line shows up in the terminal:

    The runtime has been configured to pause during startup and is awaiting a Diagnostics IPC ResumeStartup command from a Diagnostic Port

  4. Once that's printed, go ahead and start profiling:

    $ dotnet-trace collect --diagnostic-port ~/my-dev-port,connect --format speedscope

Desktop

Profiling on the desktop (both macOS and Mac Catalyst apps are the same) is much easier.

  1. Launch the executable, passing the DOTNET_DiagnosticPorts variable directly:

    $ DOTNET_DiagnosticPorts=~/my-desktop-port,suspend ./bin/Debug/net6.0-*/*/MyTestApp.app/Contents/MacOS/MyTestApp
  2. At this point it's necessary to wait until the following line shows up in the terminal:

    The runtime has been configured to pause during startup and is awaiting a Diagnostics IPC ResumeStartup command from a Diagnostic Port

  3. Once that's printed, go ahead and start profiling:

    $ dotnet-trace collect --diagnostic-port ~/my-desktop-port --format speedscope

Analyzing the results

In this guide I've selected to save the profiling results as speedscope files. These can be viewed in various ways, for instance online at https://speedscope.app.

References

Last updated