From 0f7a0cde1425c29c52789e22192d1cc4c7af2334 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 22 Jun 2021 10:18:50 +0200 Subject: [PATCH] Add EventPipe tracing support (#6022) * Add EventPipe tracing support `EventPipe` is a dotnet mechanism to implement profiling/tracing of .NET5+ applications. It replaces the Mono logging profiler which no longer works. This commit adds support of packaging the Mono components required for EventPipe to work in the application APK as well as runtime support for initializing and enabling the profiler. Profiler is enabled by setting the `debug.mono.profile` Android system property to the appropriate value (e.g. `127.0.0.1:9000,suspend`) which is then exported by the XA runtime as an environment variable (`DOTNET_DiagnosticPorts`) which, in turn, is read by the EventPipe component in order to start profiling. Details of the process are documented in the `Documentation/guides/tracing.md` file. Co-authored-by: Jonathan Peppers --- Documentation/guides/tracing.md | 93 +++++++++++++++++++ ...oft.Android.Sdk.AssemblyResolution.targets | 5 + ...soft.Android.Sdk.DefaultProperties.targets | 5 + .../Tasks/ProcessNativeLibraries.cs | 20 +++- src/monodroid/jni/monodroid-glue.cc | 20 ++++ 5 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 Documentation/guides/tracing.md 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.