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:
Copy 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:
Copy $ 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
The first step is to launch the tool that provides a connection between the app and the .NET tracing tools:
Copy $ dotnet-dsrouter client-server -ipcc ~/my-sim-port -tcps 127.0.0.1:9000
Launch the app and make it suspend upon launch (waiting for the .NET tooling to connect):
Copy $ 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'
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
Once that's printed, go ahead and start profiling:
Copy $ 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:
Copy $ 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
:
Copy $ 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:
Copy $ 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
Copy $ 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:
Copy $ 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:
Launch the tool that bridges the app and the .NET tracing tools:
Copy $ dotnet-dsrouter server-client -ipcs ~/my-dev-port -tcpc 127.0.0.1:9001 --forward-port iOS
Compared to the simulator, this:
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.
Install & launch the app and make it suspend upon launch:
Copy $ 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'
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
Once that's printed, go ahead and start profiling:
Copy $ 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.
Launch the executable, passing the DOTNET_DiagnosticPorts
variable directly:
Copy $ DOTNET_DiagnosticPorts=~/my-desktop-port,suspend ./bin/Debug/net6.0-*/*/MyTestApp.app/Contents/MacOS/MyTestApp
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
Once that's printed, go ahead and start profiling:
Copy $ 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