Skip to main content

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​