Android
Below is a guide from the .NET Android Documentation:
Using a device connected via USB
Startup profiling
Set up reverse port forwarding:
Note that you can skip this step if the Android application is running on an Android emulator; it is only required for physical Android devices.
$ adb reverse tcp:9000 tcp:9001
This will forward port 9000 on device to port 9001.
Alternatively:
$ adb reverse tcp:0 tcp:9001
43399
This will allocate a random port on remote and forward it to port 9001 on the host. The forwarded port is printed by adb
Configure the device so that the profiled app suspends until tracing utility connects
$ adb shell setprop debug.mono.profile '127.0.0.1:9000,suspend'
Install dotnet-dsrouter
dotnet-dsrouter
Generally, you can use a stable dotnet-dsrouter
from NuGet:
$ dotnet tool install -g dotnet-dsrouter
You can invoke the tool using the following command: dotnet-dsrouter
Tool 'dotnet-dsrouter' was successfully installed.
Or use a build from the nightly feed https://aka.ms/dotnet-tools/index.json
:
$ dotnet tool install -g dotnet-dsrouter --add-source=https://aka.ms/dotnet-tools/index.json --prerelease
Start the tracing router/proxy on host
For profiling an Android application running on an Android emulator:
$ dotnet-dsrouter android-emu --verbose debug
WARNING: dotnet-dsrouter is a development tool not intended for production environments.
Start an application on android emulator with one of the following environment variables set:
DOTNET_DiagnosticPorts=10.0.2.2:9000,nosuspend,connect
DOTNET_DiagnosticPorts=10.0.2.2:9000,suspend,connect
info: dotnet-dsrouter[0]
Starting dotnet-dsrouter using pid=21352
dbug: dotnet-dsrouter[0]
Using default IPC server path, dotnet-diagnostic-dsrouter-21352.
dbug: dotnet-dsrouter[0]
Attach to default dotnet-dsrouter IPC server using --process-id 21352 diagnostic tooling argument.
info: dotnet-dsrouter[0]
Starting IPC server (dotnet-diagnostic-dsrouter-21352) <--> TCP server (127.0.0.1:9000) router.
dbug: dotnet-dsrouter[0]
Trying to create new router instance.
dbug: dotnet-dsrouter[0]
Waiting for a new TCP connection at endpoint "127.0.0.1:9000".
dbug: dotnet-dsrouter[0]
Waiting for new ipc connection at endpoint "dotnet-diagnostic-dsrouter-21352".
For Android devices
For profiling an Android application running on an Android device:
$ dotnet-dsrouter server-server -tcps 127.0.0.1:9001 --verbose debug
Eventually, we will be able to simply do dotnet-dsrouter android
when dotnet/diagnostics#4337 is resolved. adb reverse tcp:9000 tcp:9001
is also currently required as mentioned above.
Start the tracing client
First, run dotnet-trace ps
to find a list of processes:
> dotnet-trace ps
38604 dotnet-dsrouter C:\Users\myuser\.dotnet\tools\dotnet-dsrouter.exe "C:\Users\myuser\.dotnet\tools\dotnet-dsrouter.exe" android-emu --verbose debug
dotnet-trace
knows how to tell if a process ID is dotnet-dsrouter
and connect through it appropriately.
Using the process ID from the previous step, run dotnet-trace collect
:
$ dotnet-trace collect -p 38604 --format speedscope
No profile or providers specified, defaulting to trace profile 'cpu-sampling'
Provider Name Keywords Level Enabled By
Microsoft-DotNETCore-SampleProfiler 0x0000F00000000000 Informational(4) --profile
Microsoft-Windows-DotNETRuntime 0x00000014C14FCCBD Informational(4) --profile
Waiting for connection on /tmp/maui-app
Start an application with the following environment variable: DOTNET_DiagnosticPorts=/tmp/maui-app
The --format
argument is optional and it defaults to nettrace
. However, nettrace
files can be viewed only with Perfview on Windows, while the speedscope JSON files can be viewed "on" Unix by uploading them to https://speedscope.app
Compile and run the application
$ dotnet build -f net8.0-android -t:Run -c Release -p:AndroidEnableProfiler=true
NOTE: -f net8.0-android
is only needed for projects with multiple $(TargetFrameworks)
.
Once the application is installed and started, dotnet-trace
should show something similar to:
Process : $HOME/.dotnet/tools/dotnet-dsrouter
Output File : /tmp/hellomaui-app-trace
[00:00:00:35] Recording trace 1.7997 (MB)
Press <Enter> or <Ctrl+C> to exit...812 (KB)
Once <Enter>
is pressed, you should see:
Stopping the trace. This may take up to minutes depending on the application being traced.
Trace completed.
Writing: hellomaui-app-trace.speedscope.json
And the output files should be found in the current directory. You can use the -o
switch if you would prefer to output them to a specific directory.
How to get GC memory dumps?
If running on desktop, you can use the dotnet-gcdump
global tool. This can be installed via:
$ dotnet tool install --global dotnet-gcdump
To use it, for example:
# `hw-readline` is a standard Hello World, with a `Console.ReadLine()` at the end
$ dotnet run --project hw-readline.csproj
Hello, World!
Press <ENTER> to continue
# Then from another shell...
# Determine which process ID to dump
$ dotnet-gcdump ps
33972 hw-readline /path/to/hw-readline/bin/Debug/hw-readline
# Collect the GC info
$ dotnet-gcdump collect -p 33972
Writing gcdump to '.../hw-readline/20230314_113922_33972.gcdump'...
Finished writing 5624131 bytes.
See the dotnet-gcdump
documentation for further details about its usage.
This will connect to a process and save a *.gcdump
file. You can open this file in Visual Studio on Windows, for example:

Memory Dumps for Android in .NET 8+
In .NET 8, we have a simplified method for collecing *.gcdump
files for Android applications. To get this data from an Android application, you need all the above setup for adb shell
, dsrouter
, etc. except you need to simply use dotnet-gcdump
instead of dotnet-trace
:
$ dotnet-gcdump collect -p 38604
This will create a *.gcdump
file in the current directory.
Memory Dumps for Android in .NET 7
In .NET 7, we have to use th older, more complicated method for collecting *.gcdump
files for Android applications. To get this data from an Android application, you need all the above setup for adb shell
, dsrouter
, etc.
$ dotnet-trace collect --diagnostic-port /tmp/maui-app --providers Microsoft-DotNETRuntimeMonoProfiler:0xC900001:4
0xC900001
, a bitmask, enables the following event types:
GCKeyword
GCHeapCollectKeyword
GCRootKeyword
See the Microsoft-DotNETRuntimeMonoProfiler
event types for more info.
:4
enables "Informational" verbosity, where the different logging levels are described by dotnet-trace help
output.
This saves a .nettrace
file with GC events that are not available with the default provider.
To actually view this data, you'll have to use one of:
Using mono-gcdump
:
$ dotnet run --project path/to/filipnavara/mono-gcdump/mono-gcdump.csproj -- convert foo.nettrace
This saves a foo.gcdump
that you can open in Visual Studio.
See the dotnet/runtime documentation for additional details.
How to dotnet trace
our build?
dotnet trace
our build?Setting this up is easy, the main issue is there end up being potentially a lot of threads (30-40) depending on the build.
Before getting started, I would recommend doing these things to make the trace smaller and easier to understand:
Set
$DOTNET_CLI_TELEMETRY_OPTOUT
to1
, to avoid any dotnet CLI telemetry in the trace.Profile a single
.csproj
build, not a.sln
. This keeps the build in-process.Always
restore
in a separate step and use--no-restore
when you trace. This avoids NuGet logic in the trace.Save a
.binlog
, so you can review that the build actually did what you expected.dotnet trace
tends to hide all the console output.
So, for example, to profile a build:
dotnet restore foo.csproj
dotnet trace collect --format speedscope -- dotnet build -bl --no-restore foo.csproj
This should result in .speedscope
and .nettrace
files in the current directory.
If you wanted to profile deploy & app launch, do a build first:
dotnet build foo.csproj
dotnet trace collect --format speedscope -- dotnet build "-t:Run" -bl --no-restore foo.csproj
I found that "
is necessary when :
characters are present in the command. This appears to be some kind of argument parsing issue with dotnet trace
.
Last updated