diff --git a/Documentation/guides/tracing.md b/Documentation/guides/tracing.md new file mode 100644 index 00000000000..3cb9a203ce5 --- /dev/null +++ b/Documentation/guides/tracing.md @@ -0,0 +1,93 @@ +# Using a device connected via USB + +## Startup profiling +### Set up reverse port forwarding: +``` +$ 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' +``` + +### Start the tracing router/proxy on host +We assume ports as given above, in the first example. +``` +$ dotnet-dsrouter client-server -tcps 127.0.0.1:9001 -ipcc /tmp/maui-app --verbose debug +WARNING: dotnet-dsrouter is an experimental development tool not intended for production environments. + +info: dotnet-dsrounter[0] + Starting IPC client (/tmp/maui-app) <--> TCP server (127.0.0.1:9001) router. +dbug: dotnet-dsrounter[0] + Trying to create a new router instance. +dbug: dotnet-dsrounter[0] + Waiting for a new tcp connection at endpoint "127.0.0.1:9001". +``` + +This starts a `dsrouter` TCP/IP server on host port `9000` and an IPC (Unix socket on *nix machines) client with the socket name/path `/tmp/maui-app` + +### Start the tracing client + +Before starting the client make sure that the socket file does **not** exist. + +``` +$ dotnet-trace collect --diagnostic-port /tmp/maui-app --format speedscope -o /tmp/hellomaui-app-trace +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 net6.0-android \ + /t:Install \ + /bl \ + /p:Configuration=Release \ + /p:AndroidLinkResources=true \ + /p:AndroidEnableProfiler=true +``` + +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 or to exit...812 (KB) +``` + +Once `` is pressed, you should see: + +``` +Stopping the trace. This may take up to minutes depending on the application being traced. + +Trace completed. +Writing: /tmp/hellomaui-app-trace.speedscope.json +``` + +And the following files should be found in `/tmp`: + +``` +$ ls -h /tmp/hellomaui-app*|cat +/tmp/hellomaui-app-trace +/tmp/hellomaui-app-trace.speedscope.json +``` + +`/tmp/hellomaui-app-trace` is the nettrace file. diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets index 0efba7c98d9..6d4e4096760 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets @@ -168,8 +168,13 @@ _ResolveAssemblies MSBuild target. <_ResolvedNativeLibraries Include="@(ResolvedFileToPublish)" Condition=" '%(ResolvedFileToPublish.Extension)' == '.so' " /> + + <_MonoComponent Condition=" '$(AndroidEnableProfiler)' == 'true' " Include="diagnostics_tracing" /> + <_MonoComponent Condition=" '$(AndroidUseInterpreter)' == 'true' " Include="hot_reload" /> + diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets index 6f434688cc3..205c6414c5d 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets @@ -25,6 +25,9 @@ --> <_GetChildProjectCopyToPublishDirectoryItems>false true + + + false @@ -82,6 +85,8 @@ false + + True 1.0 diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ProcessNativeLibraries.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ProcessNativeLibraries.cs index 23f6dd000b7..f853c2c98b2 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/ProcessNativeLibraries.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/ProcessNativeLibraries.cs @@ -13,19 +13,19 @@ namespace Xamarin.Android.Tasks /// public class ProcessNativeLibraries : AndroidTask { + const string MonoComponentPrefix = "libmono-component-"; + public override string TaskPrefix => "PRNL"; - static readonly HashSet DebugNativeLibraries = new HashSet { + static readonly HashSet DebugNativeLibraries = new HashSet (StringComparer.OrdinalIgnoreCase) { "libxamarin-debug-app-helper", - // TODO: eventually we should have a proper mechanism for including/excluding Mono components - "libmono-component-diagnostics_tracing", - "libmono-component-hot_reload", }; /// /// Assumed to be .so files only /// public ITaskItem [] InputLibraries { get; set; } + public ITaskItem [] Components { get; set; } public bool IncludeDebugSymbols { get; set; } @@ -37,6 +37,13 @@ public override bool RunTask () if (InputLibraries == null || InputLibraries.Length == 0) return true; + var wantedComponents = new HashSet (StringComparer.OrdinalIgnoreCase); + if (Components != null && Components.Length > 0) { + foreach (ITaskItem item in Components) { ; + wantedComponents.Add ($"{MonoComponentPrefix}{item.ItemSpec}"); + } + } + var output = new List (InputLibraries.Length); foreach (var library in InputLibraries) { @@ -69,7 +76,12 @@ public override bool RunTask () Log.LogDebugMessage ($"Excluding '{library.ItemSpec}' for release builds."); continue; } + } else if (fileName.StartsWith (MonoComponentPrefix, StringComparison.OrdinalIgnoreCase)) { + if (!wantedComponents.Contains (fileName)) { + continue; + } } + output.Add (library); } diff --git a/src/monodroid/jni/monodroid-glue.cc b/src/monodroid/jni/monodroid-glue.cc index 3a49fd60bbe..47dbad2322c 100644 --- a/src/monodroid/jni/monodroid-glue.cc +++ b/src/monodroid/jni/monodroid-glue.cc @@ -1475,6 +1475,25 @@ MonodroidRuntime::set_trace_options (void) mono_jit_set_trace_options (value.get ()); } +#if defined (NET6) +inline void +MonodroidRuntime::set_profile_options () +{ + // We want to avoid dynamic allocation, thus let’s create a buffer that can take both the property value and a + // path without allocation + dynamic_local_string value; + { + dynamic_local_string prop_value; + if (androidSystem.monodroid_get_system_property (Debug::DEBUG_MONO_PROFILE_PROPERTY, prop_value) == 0) + return; + + value.assign (prop_value.get (), prop_value.length ()); + } + + // setenv(3) makes copies of its arguments + setenv ("DOTNET_DiagnosticPorts", value.get (), 1); +} +#else // def NET6 inline void MonodroidRuntime::set_profile_options () { @@ -1566,6 +1585,7 @@ MonodroidRuntime::set_profile_options () log_warn (LOG_DEFAULT, "Initializing profiler with options: %s", value.get ()); debug.monodroid_profiler_load (androidSystem.get_runtime_libdir (), value.get (), output_path.get ()); } +#endif // ndef NET6 /* Disable LLVM signal handlers.