Android
Last updated
Last updated
Below is a guide from the .NET Android Documentation:
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
$ adb shell setprop debug.mono.profile '127.0.0.1:9000,suspend'
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
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 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.
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
$ 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.
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:
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.
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.
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
to 1
, 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
.