From 447c8706a0222b597a654434451b445868268121 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Jul 2026 00:17:40 +0000 Subject: [PATCH 1/5] Add interop EventSource and instrumentation hooks Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com> --- .../Documentation/EventPipeInteropEvents.md | 42 +++++ .../Java.Interop/InteropEventSource.cs | 172 ++++++++++++++++++ .../JniRuntime.JniValueManager.cs | 9 + .../JniRuntime.ReflectionJniValueManager.cs | 34 ++++ .../Java.Interop/Java.Interop/ManagedPeer.cs | 9 + .../src/Java.Interop/PublicAPI.Unshipped.txt | 8 + .../Java.Interop/InteropEventSourceTests.cs | 119 ++++++++++++ .../Android.Runtime/AndroidRuntime.cs | 10 + .../JavaMarshalRegisteredPeers.cs | 98 +++++++++- .../TrimmableTypeMapValueManager.cs | 36 +++- .../InteropEventSourceRuntimeTests.cs | 71 ++++++++ 11 files changed, 603 insertions(+), 5 deletions(-) create mode 100644 external/Java.Interop/Documentation/EventPipeInteropEvents.md create mode 100644 external/Java.Interop/src/Java.Interop/Java.Interop/InteropEventSource.cs create mode 100644 external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/InteropEventSourceTests.cs create mode 100644 tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/InteropEventSourceRuntimeTests.cs diff --git a/external/Java.Interop/Documentation/EventPipeInteropEvents.md b/external/Java.Interop/Documentation/EventPipeInteropEvents.md new file mode 100644 index 00000000000..b2c4d8937cd --- /dev/null +++ b/external/Java.Interop/Documentation/EventPipeInteropEvents.md @@ -0,0 +1,42 @@ +# Java.Interop EventPipe interop events + +The .NET ↔ Java interop layer emits EventPipe events through a single provider: + +- **Provider name:** `Java.Interop` + +## Event catalog + +| Event ID | Event name | Meaning | +|---|---|---| +| 1 | `DotNetWrapperCreated` | A managed wrapper for a Java object was created. | +| 2 | `JavaWrapperCreated` | A Java wrapper for a managed object was created. | +| 3 | `DotNetWrapperReleasedJavaReference` | A managed wrapper released its Java reference. | +| 4 | `JavaWrapperReleasedDotNetReference` | A Java wrapper released its managed reference. | +| 5 | `DotNetObjectOnlyReachableFromJava` | A managed object is only reachable from Java during bridge processing. | +| 6 | `JavaObjectOnlyReachableFromDotNet` | A Java object is only reachable from .NET during bridge processing. | + +## Payload schema + +All events contain: + +- `managedType` (`string`) +- `javaType` (`string`) +- `jniIdentityHashCode` (`int`) +- `managedObjectHashCode` (`int`) +- `runtimeMode` (`string`) + +Reachability events (`5`, `6`) additionally contain: + +- `componentIndex` (`int`) +- `contextIndex` (`int`) +- `contextPointer` (`long`) + +## Collecting events + +Use `dotnet-trace` to capture the provider: + +```bash +dotnet-trace collect --process-id --providers Java.Interop:0x3:4 +``` + +`0x3` enables both wrapper lifecycle and reachability keywords, and `4` enables informational-level events. diff --git a/external/Java.Interop/src/Java.Interop/Java.Interop/InteropEventSource.cs b/external/Java.Interop/src/Java.Interop/Java.Interop/InteropEventSource.cs new file mode 100644 index 00000000000..65896501859 --- /dev/null +++ b/external/Java.Interop/src/Java.Interop/Java.Interop/InteropEventSource.cs @@ -0,0 +1,172 @@ +#nullable enable + +using System.Diagnostics.Tracing; + +namespace Java.Interop +{ + public static class InteropEventSource + { + internal const string ProviderName = "Java.Interop"; + const string UnknownValue = "Unknown"; + + static readonly InteropEventSourceImplementation source = new InteropEventSourceImplementation (); + + public static bool IsEnabled () + { + return source.IsEnabled (); + } + + public static void DotNetWrapperCreated ( + string? managedType, + string? javaType, + int jniIdentityHashCode, + int managedObjectHashCode, + string? runtimeMode) + { + source.DotNetWrapperCreated ( + GetPayloadValue (managedType), + GetPayloadValue (javaType), + jniIdentityHashCode, + managedObjectHashCode, + GetPayloadValue (runtimeMode)); + } + + public static void JavaWrapperCreated ( + string? managedType, + string? javaType, + int jniIdentityHashCode, + int managedObjectHashCode, + string? runtimeMode) + { + source.JavaWrapperCreated ( + GetPayloadValue (managedType), + GetPayloadValue (javaType), + jniIdentityHashCode, + managedObjectHashCode, + GetPayloadValue (runtimeMode)); + } + + public static void DotNetWrapperReleasedJavaReference ( + string? managedType, + string? javaType, + int jniIdentityHashCode, + int managedObjectHashCode, + string? runtimeMode) + { + source.DotNetWrapperReleasedJavaReference ( + GetPayloadValue (managedType), + GetPayloadValue (javaType), + jniIdentityHashCode, + managedObjectHashCode, + GetPayloadValue (runtimeMode)); + } + + public static void JavaWrapperReleasedDotNetReference ( + string? managedType, + string? javaType, + int jniIdentityHashCode, + int managedObjectHashCode, + string? runtimeMode) + { + source.JavaWrapperReleasedDotNetReference ( + GetPayloadValue (managedType), + GetPayloadValue (javaType), + jniIdentityHashCode, + managedObjectHashCode, + GetPayloadValue (runtimeMode)); + } + + public static void DotNetObjectOnlyReachableFromJava ( + string? managedType, + string? javaType, + int jniIdentityHashCode, + int managedObjectHashCode, + string? runtimeMode, + int componentIndex, + int contextIndex, + long contextPointer) + { + source.DotNetObjectOnlyReachableFromJava ( + GetPayloadValue (managedType), + GetPayloadValue (javaType), + jniIdentityHashCode, + managedObjectHashCode, + GetPayloadValue (runtimeMode), + componentIndex, + contextIndex, + contextPointer); + } + + public static void JavaObjectOnlyReachableFromDotNet ( + string? managedType, + string? javaType, + int jniIdentityHashCode, + int managedObjectHashCode, + string? runtimeMode, + int componentIndex, + int contextIndex, + long contextPointer) + { + source.JavaObjectOnlyReachableFromDotNet ( + GetPayloadValue (managedType), + GetPayloadValue (javaType), + jniIdentityHashCode, + managedObjectHashCode, + GetPayloadValue (runtimeMode), + componentIndex, + contextIndex, + contextPointer); + } + + static string GetPayloadValue (string? value) + { + return value ?? UnknownValue; + } + + [EventSource (Name = ProviderName)] + sealed class InteropEventSourceImplementation : EventSource + { + public static class Keywords + { + public const EventKeywords WrapperLifecycle = (EventKeywords) 0x1; + public const EventKeywords Reachability = (EventKeywords) 0x2; + } + + [Event (1, Level = EventLevel.Informational, Keywords = Keywords.WrapperLifecycle)] + public void DotNetWrapperCreated (string managedType, string javaType, int jniIdentityHashCode, int managedObjectHashCode, string runtimeMode) + { + WriteEvent (1, managedType, javaType, jniIdentityHashCode, managedObjectHashCode, runtimeMode); + } + + [Event (2, Level = EventLevel.Informational, Keywords = Keywords.WrapperLifecycle)] + public void JavaWrapperCreated (string managedType, string javaType, int jniIdentityHashCode, int managedObjectHashCode, string runtimeMode) + { + WriteEvent (2, managedType, javaType, jniIdentityHashCode, managedObjectHashCode, runtimeMode); + } + + [Event (3, Level = EventLevel.Informational, Keywords = Keywords.WrapperLifecycle)] + public void DotNetWrapperReleasedJavaReference (string managedType, string javaType, int jniIdentityHashCode, int managedObjectHashCode, string runtimeMode) + { + WriteEvent (3, managedType, javaType, jniIdentityHashCode, managedObjectHashCode, runtimeMode); + } + + [Event (4, Level = EventLevel.Informational, Keywords = Keywords.WrapperLifecycle)] + public void JavaWrapperReleasedDotNetReference (string managedType, string javaType, int jniIdentityHashCode, int managedObjectHashCode, string runtimeMode) + { + WriteEvent (4, managedType, javaType, jniIdentityHashCode, managedObjectHashCode, runtimeMode); + } + + [Event (5, Level = EventLevel.Informational, Keywords = Keywords.Reachability)] + public void DotNetObjectOnlyReachableFromJava (string managedType, string javaType, int jniIdentityHashCode, int managedObjectHashCode, string runtimeMode, int componentIndex, int contextIndex, long contextPointer) + { + WriteEvent (5, managedType, javaType, jniIdentityHashCode, managedObjectHashCode, runtimeMode, componentIndex, contextIndex, contextPointer); + } + + [Event (6, Level = EventLevel.Informational, Keywords = Keywords.Reachability)] + public void JavaObjectOnlyReachableFromDotNet (string managedType, string javaType, int jniIdentityHashCode, int managedObjectHashCode, string runtimeMode, int componentIndex, int contextIndex, long contextPointer) + { + WriteEvent (6, managedType, javaType, jniIdentityHashCode, managedObjectHashCode, runtimeMode, componentIndex, contextIndex, contextPointer); + } + } + } +} diff --git a/external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs b/external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs index a5383701a1e..c2677c8f2b6 100644 --- a/external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs +++ b/external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs @@ -116,6 +116,15 @@ public virtual void DisposePeer (IJavaPeerable value) var h = value.PeerReference; if (!h.IsValid) return; + if (InteropEventSource.IsEnabled ()) { + var javaType = JniEnvironment.Types.GetJniTypeNameFromInstance (h); + InteropEventSource.DotNetWrapperReleasedJavaReference ( + value.GetType ().FullName, + javaType, + value.JniIdentityHashCode, + RuntimeHelpers.GetHashCode (value), + Runtime.GetType ().FullName); + } DisposePeer (h, value); } diff --git a/external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs b/external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs index 56f836bd2e3..9be4ea9b6b8 100644 --- a/external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs +++ b/external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs @@ -78,6 +78,9 @@ protected override void ConstructPeerCore (IJavaPeerable peer, ref JniObjectRefe peer.SetPeerReference (newRef); peer.SetJniIdentityHashCode (JniSystem.IdentityHashCode (newRef)); + if (!peer.JniManagedPeerState.HasFlag (JniManagedPeerStates.Activatable) && InteropEventSource.IsEnabled ()) { + EmitJavaWrapperCreatedEvent (peer, newRef); + } var o = Runtime.ObjectReferenceManager; if (o.LogGlobalReferenceMessages) { @@ -94,6 +97,34 @@ protected override void ConstructPeerCore (IJavaPeerable peer, ref JniObjectRefe } } + void EmitDotNetWrapperCreatedEvent (IJavaPeerable peer) + { + JniObjectReference reference = peer.PeerReference; + var javaType = reference.IsValid ? JniEnvironment.Types.GetJniTypeNameFromInstance (reference) : null; + InteropEventSource.DotNetWrapperCreated ( + peer.GetType ().FullName, + javaType, + peer.JniIdentityHashCode, + RuntimeHelpers.GetHashCode (peer), + GetRuntimeMode ()); + } + + void EmitJavaWrapperCreatedEvent (IJavaPeerable peer, JniObjectReference reference) + { + var javaType = reference.IsValid ? JniEnvironment.Types.GetJniTypeNameFromInstance (reference) : null; + InteropEventSource.JavaWrapperCreated ( + peer.GetType ().FullName, + javaType, + peer.JniIdentityHashCode, + RuntimeHelpers.GetHashCode (peer), + GetRuntimeMode ()); + } + + string GetRuntimeMode () + { + return Runtime.GetType ().FullName ?? "Unknown"; + } + // This base method implementation is NOT reachable in trimmable typemap - it is featureswitch guarded public override IJavaPeerable? CreatePeer ( ref JniObjectReference reference, @@ -143,6 +174,9 @@ protected override void ConstructPeerCore (IJavaPeerable peer, ref JniObjectRefe JniEnvironment.Types.GetJniTypeNameFromInstance (reference), targetType)); } peer.SetJniManagedPeerState (peer.JniManagedPeerState | JniManagedPeerStates.Replaceable); + if (InteropEventSource.IsEnabled ()) { + EmitDotNetWrapperCreatedEvent (peer); + } return peer; } diff --git a/external/Java.Interop/src/Java.Interop/Java.Interop/ManagedPeer.cs b/external/Java.Interop/src/Java.Interop/Java.Interop/ManagedPeer.cs index d026789a180..a84ceccdd6c 100644 --- a/external/Java.Interop/src/Java.Interop/Java.Interop/ManagedPeer.cs +++ b/external/Java.Interop/src/Java.Interop/Java.Interop/ManagedPeer.cs @@ -105,6 +105,15 @@ static void Construct ( var typeSig = new JniTypeSignature (JniEnvironment.Types.GetJniTypeNameFromInstance (r_self)); var type = GetTypeFromSignature (runtime.TypeManager, typeSig); + if (InteropEventSource.IsEnabled ()) { + var managedObjectHashCode = self != null ? RuntimeHelpers.GetHashCode (self) : 0; + InteropEventSource.JavaWrapperCreated ( + type.FullName, + typeSig.SimpleReference, + runtime.ValueManager.GetJniIdentityHashCode (r_self), + managedObjectHashCode, + runtime.GetType ().FullName); + } if (type.IsGenericTypeDefinition) { throw new NotSupportedException ( diff --git a/external/Java.Interop/src/Java.Interop/PublicAPI.Unshipped.txt b/external/Java.Interop/src/Java.Interop/PublicAPI.Unshipped.txt index 2088a07dc59..2a00d8231db 100644 --- a/external/Java.Interop/src/Java.Interop/PublicAPI.Unshipped.txt +++ b/external/Java.Interop/src/Java.Interop/PublicAPI.Unshipped.txt @@ -117,3 +117,11 @@ override Java.Interop.JniRuntime.ReflectionJniTypeManager.GetTypesForSimpleRefer override Java.Interop.JniRuntime.ReflectionJniTypeManager.RegisterNativeMembers(Java.Interop.JniType! nativeClass, System.Type! type, string? methods) -> void override Java.Interop.JniRuntime.ReflectionJniTypeManager.RegisterNativeMembers(Java.Interop.JniType! nativeClass, System.Type! type, System.ReadOnlySpan methods) -> void virtual Java.Interop.JniRuntime.ReflectionJniValueManager.TryConstructPeer(Java.Interop.IJavaPeerable! self, ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type! type) -> bool +static Java.Interop.InteropEventSource.DotNetObjectOnlyReachableFromJava(string? managedType, string? javaType, int jniIdentityHashCode, int managedObjectHashCode, string? runtimeMode, int componentIndex, int contextIndex, long contextPointer) -> void +static Java.Interop.InteropEventSource.DotNetWrapperCreated(string? managedType, string? javaType, int jniIdentityHashCode, int managedObjectHashCode, string? runtimeMode) -> void +static Java.Interop.InteropEventSource.DotNetWrapperReleasedJavaReference(string? managedType, string? javaType, int jniIdentityHashCode, int managedObjectHashCode, string? runtimeMode) -> void +static Java.Interop.InteropEventSource.IsEnabled() -> bool +static Java.Interop.InteropEventSource.JavaObjectOnlyReachableFromDotNet(string? managedType, string? javaType, int jniIdentityHashCode, int managedObjectHashCode, string? runtimeMode, int componentIndex, int contextIndex, long contextPointer) -> void +static Java.Interop.InteropEventSource.JavaWrapperCreated(string? managedType, string? javaType, int jniIdentityHashCode, int managedObjectHashCode, string? runtimeMode) -> void +static Java.Interop.InteropEventSource.JavaWrapperReleasedDotNetReference(string? managedType, string? javaType, int jniIdentityHashCode, int managedObjectHashCode, string? runtimeMode) -> void +Java.Interop.InteropEventSource diff --git a/external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/InteropEventSourceTests.cs b/external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/InteropEventSourceTests.cs new file mode 100644 index 00000000000..5566a0c1ed0 --- /dev/null +++ b/external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/InteropEventSourceTests.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Tracing; +using System.Linq; + +using Java.Interop; + +using NUnit.Framework; + +namespace Java.InteropTests +{ + [TestFixture] + public class InteropEventSourceTests + { + [Test] + public void WrapperLifecycleEvents_HaveExpectedPayload () + { + using (var listener = new CapturingEventListener ()) { + InteropEventSource.DotNetWrapperCreated ("Managed.Type", "java/type", 1, 2, "CoreCLR"); + InteropEventSource.JavaWrapperCreated ("Managed.Type", "java/type", 3, 4, "CoreCLR"); + InteropEventSource.DotNetWrapperReleasedJavaReference ("Managed.Type", "java/type", 5, 6, "CoreCLR"); + InteropEventSource.JavaWrapperReleasedDotNetReference ("Managed.Type", "java/type", 7, 8, "CoreCLR"); + + var lifecycleEvents = listener.Events.Where (e => e.EventId is >= 1 and <= 4).ToArray (); + Assert.AreEqual (4, lifecycleEvents.Length, "Expected all lifecycle events to be emitted."); + + AssertEventPayload (lifecycleEvents [0], "DotNetWrapperCreated", "Managed.Type", "java/type", 1, 2, "CoreCLR"); + AssertEventPayload (lifecycleEvents [1], "JavaWrapperCreated", "Managed.Type", "java/type", 3, 4, "CoreCLR"); + AssertEventPayload (lifecycleEvents [2], "DotNetWrapperReleasedJavaReference", "Managed.Type", "java/type", 5, 6, "CoreCLR"); + AssertEventPayload (lifecycleEvents [3], "JavaWrapperReleasedDotNetReference", "Managed.Type", "java/type", 7, 8, "CoreCLR"); + } + } + + [Test] + public void ReachabilityEvents_HaveExpectedPayload () + { + using (var listener = new CapturingEventListener ()) { + InteropEventSource.DotNetObjectOnlyReachableFromJava ("Managed.Type", "java/type", 11, 12, "NativeAOT", 2, 3, 16); + InteropEventSource.JavaObjectOnlyReachableFromDotNet ("Managed.Type", "java/type", 21, 22, "NativeAOT", 4, 5, 32); + + var reachabilityEvents = listener.Events.Where (e => e.EventId is >= 5 and <= 6).ToArray (); + Assert.AreEqual (2, reachabilityEvents.Length, "Expected both reachability events to be emitted."); + + AssertReachabilityPayload (reachabilityEvents [0], "DotNetObjectOnlyReachableFromJava", "Managed.Type", "java/type", 11, 12, "NativeAOT", 2, 3, 16L); + AssertReachabilityPayload (reachabilityEvents [1], "JavaObjectOnlyReachableFromDotNet", "Managed.Type", "java/type", 21, 22, "NativeAOT", 4, 5, 32L); + } + } + + [Test] + public void CallsWithoutListener_DoNotThrow () + { + Assert.DoesNotThrow (() => InteropEventSource.DotNetWrapperCreated ("Managed.Type", "java/type", 1, 2, "MonoVM")); + Assert.DoesNotThrow (() => InteropEventSource.JavaWrapperCreated ("Managed.Type", "java/type", 1, 2, "MonoVM")); + Assert.DoesNotThrow (() => InteropEventSource.DotNetWrapperReleasedJavaReference ("Managed.Type", "java/type", 1, 2, "MonoVM")); + Assert.DoesNotThrow (() => InteropEventSource.JavaWrapperReleasedDotNetReference ("Managed.Type", "java/type", 1, 2, "MonoVM")); + Assert.DoesNotThrow (() => InteropEventSource.DotNetObjectOnlyReachableFromJava ("Managed.Type", "java/type", 1, 2, "MonoVM", 1, 1, 1)); + Assert.DoesNotThrow (() => InteropEventSource.JavaObjectOnlyReachableFromDotNet ("Managed.Type", "java/type", 1, 2, "MonoVM", 1, 1, 1)); + } + + static void AssertEventPayload (CapturedEvent captured, string eventName, string managedType, string javaType, int jniHash, int managedHash, string runtimeMode) + { + Assert.AreEqual (eventName, captured.EventName); + Assert.AreEqual (managedType, captured.Payload [0]); + Assert.AreEqual (javaType, captured.Payload [1]); + Assert.AreEqual (jniHash, captured.Payload [2]); + Assert.AreEqual (managedHash, captured.Payload [3]); + Assert.AreEqual (runtimeMode, captured.Payload [4]); + } + + static void AssertReachabilityPayload (CapturedEvent captured, string eventName, string managedType, string javaType, int jniHash, int managedHash, string runtimeMode, int componentIndex, int contextIndex, long contextPointer) + { + Assert.AreEqual (eventName, captured.EventName); + Assert.AreEqual (managedType, captured.Payload [0]); + Assert.AreEqual (javaType, captured.Payload [1]); + Assert.AreEqual (jniHash, captured.Payload [2]); + Assert.AreEqual (managedHash, captured.Payload [3]); + Assert.AreEqual (runtimeMode, captured.Payload [4]); + Assert.AreEqual (componentIndex, captured.Payload [5]); + Assert.AreEqual (contextIndex, captured.Payload [6]); + Assert.AreEqual (contextPointer, captured.Payload [7]); + } + + readonly struct CapturedEvent + { + public string EventName { get; } + public int EventId { get; } + public object?[] Payload { get; } + + public CapturedEvent (string eventName, int eventId, object?[] payload) + { + EventName = eventName; + EventId = eventId; + Payload = payload; + } + } + + sealed class CapturingEventListener : EventListener + { + public List Events { get; } = new List (); + + protected override void OnEventSourceCreated (EventSource eventSource) + { + if (eventSource.Name == InteropEventSource.ProviderName) { + EnableEvents (eventSource, EventLevel.Verbose, EventKeywords.All); + } + } + + protected override void OnEventWritten (EventWrittenEventArgs eventData) + { + if (eventData.EventName == null) { + return; + } + + var payload = eventData.Payload?.ToArray () ?? Array.Empty (); + Events.Add (new CapturedEvent (eventData.EventName, eventData.EventId, payload)); + } + } + } +} diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index 34bafabbe58..b7001a8e906 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -626,6 +626,16 @@ public override void WaitForGCBridgeProcessing () return null; var peer = Java.Interop.TypeManager.CreateInstance (reference.Handle, JniHandleOwnership.DoNotTransfer, targetType) as IJavaPeerable; + if (peer != null && InteropEventSource.IsEnabled ()) { + var peerReference = peer.PeerReference; + var javaType = peerReference.IsValid ? JniEnvironment.Types.GetJniTypeNameFromInstance (peerReference) : null; + InteropEventSource.DotNetWrapperCreated ( + peer.GetType ().FullName, + javaType, + peer.JniIdentityHashCode, + RuntimeHelpers.GetHashCode (peer), + "MonoVM"); + } JniObjectReference.Dispose (ref reference, options); return peer; } diff --git a/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalRegisteredPeers.cs b/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalRegisteredPeers.cs index 17aa6fe4e71..494963dcaec 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalRegisteredPeers.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalRegisteredPeers.cs @@ -192,6 +192,9 @@ public static void RemovePeer (IJavaPeerable value) ReferenceTrackingHandle peer = peers [i]; IJavaPeerable? target = peer.Target; if (ReferenceEquals (value, target)) { + if (InteropEventSource.IsEnabled ()) { + EmitJavaWrapperReleasedDotNetReference (value); + } peers.RemoveAt (i); peer.Dispose (); } @@ -232,6 +235,9 @@ public static void FinalizePeer (IJavaPeerable value) RuntimeHelpers.GetHashCode (value).ToString ("x", CultureInfo.InvariantCulture), value.GetType ().ToString ()); } + if (InteropEventSource.IsEnabled ()) { + EmitDotNetWrapperReleasedJavaReference (value, h); + } value.SetPeerReference (new JniObjectReference ()); JniObjectReference.Dispose (ref h); value.Finalized (); @@ -255,6 +261,84 @@ public static List GetSurfacedPeers () } } + static void EmitJavaWrapperReleasedDotNetReference (IJavaPeerable peer) + { + JniObjectReference reference = peer.PeerReference; + var javaType = reference.IsValid ? JniEnvironment.Types.GetJniTypeNameFromInstance (reference) : null; + InteropEventSource.JavaWrapperReleasedDotNetReference ( + peer.GetType ().FullName, + javaType, + peer.JniIdentityHashCode, + RuntimeHelpers.GetHashCode (peer), + GetRuntimeMode ()); + } + + static void EmitDotNetWrapperReleasedJavaReference (IJavaPeerable peer, JniObjectReference reference) + { + var javaType = reference.IsValid ? JniEnvironment.Types.GetJniTypeNameFromInstance (reference) : null; + InteropEventSource.DotNetWrapperReleasedJavaReference ( + peer.GetType ().FullName, + javaType, + peer.JniIdentityHashCode, + RuntimeHelpers.GetHashCode (peer), + GetRuntimeMode ()); + } + + unsafe static void EmitReachabilityEventIfEnabled (HandleContext* context, GCHandle handle, int componentIndex, int contextIndex, bool isCollected) + { + if (!InteropEventSource.IsEnabled ()) { + return; + } + + IJavaPeerable? peer = handle.Target as IJavaPeerable; + string? managedType = peer?.GetType ().FullName; + int managedObjectHashCode = peer != null ? RuntimeHelpers.GetHashCode (peer) : 0; + string? javaType = null; + if (context->controlBlock != IntPtr.Zero) { + IntPtr javaHandle = ((JniObjectReferenceControlBlock*) context->controlBlock)->handle; + if (javaHandle != IntPtr.Zero) { + javaType = JniEnvironment.Types.GetJniTypeNameFromInstance (new JniObjectReference (javaHandle, JniObjectReferenceType.Global)); + } + } + + if (isCollected) { + InteropEventSource.JavaObjectOnlyReachableFromDotNet ( + managedType, + javaType, + context->PeerIdentityHashCode, + managedObjectHashCode, + GetRuntimeMode (), + componentIndex, + contextIndex, + (long) (nint) context); + return; + } + + InteropEventSource.DotNetObjectOnlyReachableFromJava ( + managedType, + javaType, + context->PeerIdentityHashCode, + managedObjectHashCode, + GetRuntimeMode (), + componentIndex, + contextIndex, + (long) (nint) context); + } + + static string GetRuntimeMode () + { + if (RuntimeFeature.IsNativeAotRuntime) { + return "NativeAOT"; + } + if (RuntimeFeature.IsCoreClrRuntime) { + return "CoreCLR"; + } + if (RuntimeFeature.IsMonoRuntime) { + return "MonoVM"; + } + return "Unknown"; + } + unsafe struct ReferenceTrackingHandle : IDisposable { WeakReference _weakReference; @@ -437,24 +521,30 @@ static unsafe ReadOnlySpan ProcessCollectedContexts (MarkCrossReferenc for (int i = 0; (nuint)i < mcr->ComponentCount; i++) { StronglyConnectedComponent component = mcr->Components [i]; for (int j = 0; (nuint)j < component.Count; j++) { - ProcessContext ((HandleContext*)component.Contexts [j]); + ProcessContext ((HandleContext*)component.Contexts [j], i, j); } } #pragma warning restore CA1416 - void ProcessContext (HandleContext* context) + void ProcessContext (HandleContext* context, int componentIndex, int contextIndex) { if (context == null) { throw new ArgumentNullException (nameof (context), "HandleContext should never be null."); } - // Ignore contexts which were not collected - if (!context->IsCollected) { + bool isCollected = context->IsCollected; + if (!isCollected && !InteropEventSource.IsEnabled ()) { return; } GCHandle handle = HandleContext.GetAssociatedGCHandle (context); + EmitReachabilityEventIfEnabled (context, handle, componentIndex, contextIndex, isCollected); + + // Ignore contexts which were not collected + if (!isCollected) { + return; + } // Note: modifying the RegisteredInstances dictionary while processing the collected contexts // is tricky and can lead to deadlocks, so we remember which contexts were collected and we will free diff --git a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapValueManager.cs b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapValueManager.cs index b2344f8fd2a..12aa1c236ea 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapValueManager.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapValueManager.cs @@ -100,6 +100,15 @@ protected override void ConstructPeerCore ( peer.SetPeerReference (newRef); peer.SetJniIdentityHashCode (JniEnvironment.References.GetIdentityHashCode (newRef)); + if (!peer.JniManagedPeerState.HasFlag (JniManagedPeerStates.Activatable) && InteropEventSource.IsEnabled ()) { + var javaType = newRef.IsValid ? JniEnvironment.Types.GetJniTypeNameFromInstance (newRef) : null; + InteropEventSource.JavaWrapperCreated ( + peer.GetType ().FullName, + javaType, + peer.JniIdentityHashCode, + RuntimeHelpers.GetHashCode (peer), + GetRuntimeMode ()); + } var o = Runtime.ObjectReferenceManager; if (o.LogGlobalReferenceMessages) { @@ -130,8 +139,19 @@ protected override void ConstructPeerCore ( try { var resolvedTargetType = ResolvePeerType (targetType); - return TrimmableTypeMap.Instance.CreateInstance (reference.Handle, resolvedTargetType) + var peer = TrimmableTypeMap.Instance.CreateInstance (reference.Handle, resolvedTargetType) ?? NotFoundFallback (ref reference, targetType, resolvedTargetType); + if (peer != null && InteropEventSource.IsEnabled ()) { + var peerReference = peer.PeerReference; + var javaType = peerReference.IsValid ? JniEnvironment.Types.GetJniTypeNameFromInstance (peerReference) : null; + InteropEventSource.DotNetWrapperCreated ( + peer.GetType ().FullName, + javaType, + peer.JniIdentityHashCode, + RuntimeHelpers.GetHashCode (peer), + GetRuntimeMode ()); + } + return peer; } finally { JniObjectReference.Dispose (ref reference, transfer); } @@ -219,6 +239,20 @@ static bool IsIncompatibleCast ( return false; } + static string GetRuntimeMode () + { + if (RuntimeFeature.IsNativeAotRuntime) { + return "NativeAOT"; + } + if (RuntimeFeature.IsCoreClrRuntime) { + return "CoreCLR"; + } + if (RuntimeFeature.IsMonoRuntime) { + return "MonoVM"; + } + return "Unknown"; + } + } [return: MaybeNull] diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/InteropEventSourceRuntimeTests.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/InteropEventSourceRuntimeTests.cs new file mode 100644 index 00000000000..6b335988517 --- /dev/null +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/InteropEventSourceRuntimeTests.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Tracing; + +using Android.Runtime; + +using Java.Interop; + +using NUnit.Framework; + +namespace Java.InteropTests +{ + [TestFixture] + public class InteropEventSourceRuntimeTests + { + [Test] + public void ManagedConstructionAndDispose_EmitLifecycleEvents () + { + using (var listener = new CapturingEventListener ()) { + using (var instance = new Java.Lang.Object ()) { + } + + Assert.IsTrue (listener.EventNames.Contains ("JavaWrapperCreated"), "Expected JavaWrapperCreated event."); + Assert.IsTrue (listener.EventNames.Contains ("DotNetWrapperReleasedJavaReference"), "Expected DotNetWrapperReleasedJavaReference event."); + } + } + + [Test] + public void WrappingRawJavaInstance_EmitsDotNetWrapperCreated () + { + using (var listener = new CapturingEventListener ()) { + IntPtr klass = JNIEnv.FindClass ("java/lang/Object"); + Assert.AreNotEqual (IntPtr.Zero, klass, "Failed to resolve java/lang/Object class."); + try { + IntPtr ctor = JNIEnv.GetMethodID (klass, "", "()V"); + Assert.AreNotEqual (IntPtr.Zero, ctor, "Failed to resolve java/lang/Object constructor."); + + IntPtr handle = JNIEnv.NewObject (klass, ctor); + Assert.AreNotEqual (IntPtr.Zero, handle, "Failed to create java/lang/Object instance."); + + var wrapper = Java.Lang.Object.GetObject (handle, JniHandleOwnership.TransferLocalRef); + Assert.IsNotNull (wrapper); + wrapper.Dispose (); + } finally { + JNIEnv.DeleteLocalRef (klass); + } + + Assert.IsTrue (listener.EventNames.Contains ("DotNetWrapperCreated"), "Expected DotNetWrapperCreated event."); + } + } + + sealed class CapturingEventListener : EventListener + { + public HashSet EventNames { get; } = new HashSet (StringComparer.Ordinal); + + protected override void OnEventSourceCreated (EventSource eventSource) + { + if (eventSource.Name == "Java.Interop") { + EnableEvents (eventSource, EventLevel.Verbose, EventKeywords.All); + } + } + + protected override void OnEventWritten (EventWrittenEventArgs eventData) + { + if (eventData.EventName != null) { + EventNames.Add (eventData.EventName); + } + } + } + } +} From e9eee278c62424633855cb5b7f95ad654a5c1036 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Jul 2026 00:19:02 +0000 Subject: [PATCH 2/5] Add interop EventPipe tests and fixups Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com> --- .../Java.Interop/src/Java.Interop/Java.Interop/ManagedPeer.cs | 1 + .../Java.Interop-Tests/Java.Interop/InteropEventSourceTests.cs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/external/Java.Interop/src/Java.Interop/Java.Interop/ManagedPeer.cs b/external/Java.Interop/src/Java.Interop/Java.Interop/ManagedPeer.cs index a84ceccdd6c..6c5e14b5276 100644 --- a/external/Java.Interop/src/Java.Interop/Java.Interop/ManagedPeer.cs +++ b/external/Java.Interop/src/Java.Interop/Java.Interop/ManagedPeer.cs @@ -8,6 +8,7 @@ using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Text; diff --git a/external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/InteropEventSourceTests.cs b/external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/InteropEventSourceTests.cs index 5566a0c1ed0..b361909ae20 100644 --- a/external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/InteropEventSourceTests.cs +++ b/external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/InteropEventSourceTests.cs @@ -1,3 +1,5 @@ +#nullable enable + using System; using System.Collections.Generic; using System.Diagnostics.Tracing; From 51b45bd93f4ab39d53c11445a0ab29f69e90f224 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Jul 2026 21:24:09 +0000 Subject: [PATCH 3/5] Address EventPipe review feedback Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com> --- .../Documentation/EventPipeInteropEvents.md | 37 +++-- .../Java.Interop/InteropEventSource.cs | 129 +++++++++++------- .../JniRuntime.JniValueManager.cs | 7 +- .../JniRuntime.ReflectionJniValueManager.cs | 27 ++-- .../Java.Interop/Java.Interop/ManagedPeer.cs | 7 +- .../Java.Interop/RuntimeFeature.cs | 13 ++ .../src/Java.Interop/PublicAPI.Unshipped.txt | 8 -- .../Java.Interop/InteropEventSourceTests.cs | 44 +++--- .../Mono.Android.Runtime.csproj | 1 + .../Android.Runtime/AndroidRuntime.cs | 7 +- .../JavaMarshalRegisteredPeers.cs | 51 +++---- .../RuntimeFeature.cs | 11 ++ .../TrimmableTypeMapValueManager.cs | 28 +--- ...icrosoft.Android.Sdk.RuntimeConfig.targets | 9 ++ .../InteropEventSourceRuntimeTests.cs | 10 +- .../Mono.Android.NET-Tests.csproj | 1 + 16 files changed, 206 insertions(+), 184 deletions(-) diff --git a/external/Java.Interop/Documentation/EventPipeInteropEvents.md b/external/Java.Interop/Documentation/EventPipeInteropEvents.md index b2c4d8937cd..712af9ed70c 100644 --- a/external/Java.Interop/Documentation/EventPipeInteropEvents.md +++ b/external/Java.Interop/Documentation/EventPipeInteropEvents.md @@ -1,19 +1,20 @@ # Java.Interop EventPipe interop events -The .NET ↔ Java interop layer emits EventPipe events through a single provider: +The .NET ↔ Java interop layer emits EventPipe events through two providers: -- **Provider name:** `Java.Interop` +- `Java.Interop` emits events from the shared Java interop layer. +- `Microsoft.Android.Runtime` emits events from the Android runtime layer. ## Event catalog | Event ID | Event name | Meaning | |---|---|---| -| 1 | `DotNetWrapperCreated` | A managed wrapper for a Java object was created. | -| 2 | `JavaWrapperCreated` | A Java wrapper for a managed object was created. | -| 3 | `DotNetWrapperReleasedJavaReference` | A managed wrapper released its Java reference. | -| 4 | `JavaWrapperReleasedDotNetReference` | A Java wrapper released its managed reference. | -| 5 | `DotNetObjectOnlyReachableFromJava` | A managed object is only reachable from Java during bridge processing. | -| 6 | `JavaObjectOnlyReachableFromDotNet` | A Java object is only reachable from .NET during bridge processing. | +| 1 | `ManagedPeerCreated` | A managed peer for a Java peer was created. | +| 2 | `JavaPeerCreated` | A Java peer for a managed peer was created. | +| 3 | `ManagedPeerReleasedJavaPeer` | A managed peer released its Java peer. | +| 4 | `JavaPeerReleasedManagedPeer` | A Java peer released its managed peer. | +| 5 | `ManagedPeerOnlyReachableFromJavaPeer` | A managed peer is only reachable from its Java peer during bridge processing. | +| 6 | `JavaPeerOnlyReachableFromManagedPeer` | A Java peer is only reachable from its managed peer during bridge processing. | ## Payload schema @@ -23,7 +24,7 @@ All events contain: - `javaType` (`string`) - `jniIdentityHashCode` (`int`) - `managedObjectHashCode` (`int`) -- `runtimeMode` (`string`) +- `runtimeFlavor` (`string`): `MonoVM`, `CoreCLR`, `NativeAOT`, or `Unknown` Reachability events (`5`, `6`) additionally contain: @@ -31,12 +32,24 @@ Reachability events (`5`, `6`) additionally contain: - `contextIndex` (`int`) - `contextPointer` (`long`) +## Enabling events + +Interop events are disabled by default so that unused instrumentation can be removed by trimming. Enable them in the application project: + +```xml + + <_AndroidEnableInteropEventSource>true + +``` + ## Collecting events -Use `dotnet-trace` to capture the provider: +Use `dotnet-trace` to capture both providers: ```bash -dotnet-trace collect --process-id --providers Java.Interop:0x3:4 +dotnet-trace collect --process-id --providers Java.Interop:0x3:4,Microsoft.Android.Runtime:0x3:4 ``` -`0x3` enables both wrapper lifecycle and reachability keywords, and `4` enables informational-level events. +`0x3` enables both peer lifecycle and reachability keywords, and `4` enables informational-level events. + +These structured events supplement the existing text-based global and local JNI reference logs; they do not replace them. diff --git a/external/Java.Interop/src/Java.Interop/Java.Interop/InteropEventSource.cs b/external/Java.Interop/src/Java.Interop/Java.Interop/InteropEventSource.cs index 65896501859..5fa2c81971b 100644 --- a/external/Java.Interop/src/Java.Interop/Java.Interop/InteropEventSource.cs +++ b/external/Java.Interop/src/Java.Interop/Java.Interop/InteropEventSource.cs @@ -1,118 +1,128 @@ #nullable enable +using System; using System.Diagnostics.Tracing; +#if INSIDE_MONO_ANDROID_RUNTIME +namespace Microsoft.Android.Runtime +#else namespace Java.Interop +#endif { - public static class InteropEventSource + internal static class InteropEventSource { +#if INSIDE_MONO_ANDROID_RUNTIME + internal const string ProviderName = "Microsoft.Android.Runtime"; +#else internal const string ProviderName = "Java.Interop"; +#endif const string UnknownValue = "Unknown"; + const string RuntimeFeatureSwitchPrefix = "Microsoft.Android.Runtime.RuntimeFeature."; + + internal static class Keywords + { + public const EventKeywords PeerLifecycle = (EventKeywords) 0x1; + public const EventKeywords Reachability = (EventKeywords) 0x2; + } static readonly InteropEventSourceImplementation source = new InteropEventSourceImplementation (); - public static bool IsEnabled () + internal static bool IsEnabled (EventKeywords keywords) { - return source.IsEnabled (); + return source.IsEnabled (EventLevel.Informational, keywords); } - public static void DotNetWrapperCreated ( + internal static void ManagedPeerCreated ( string? managedType, string? javaType, int jniIdentityHashCode, - int managedObjectHashCode, - string? runtimeMode) + int managedObjectHashCode) { - source.DotNetWrapperCreated ( + source.ManagedPeerCreated ( GetPayloadValue (managedType), GetPayloadValue (javaType), jniIdentityHashCode, managedObjectHashCode, - GetPayloadValue (runtimeMode)); + GetRuntimeFlavor ()); } - public static void JavaWrapperCreated ( + internal static void JavaPeerCreated ( string? managedType, string? javaType, int jniIdentityHashCode, - int managedObjectHashCode, - string? runtimeMode) + int managedObjectHashCode) { - source.JavaWrapperCreated ( + source.JavaPeerCreated ( GetPayloadValue (managedType), GetPayloadValue (javaType), jniIdentityHashCode, managedObjectHashCode, - GetPayloadValue (runtimeMode)); + GetRuntimeFlavor ()); } - public static void DotNetWrapperReleasedJavaReference ( + internal static void ManagedPeerReleasedJavaPeer ( string? managedType, string? javaType, int jniIdentityHashCode, - int managedObjectHashCode, - string? runtimeMode) + int managedObjectHashCode) { - source.DotNetWrapperReleasedJavaReference ( + source.ManagedPeerReleasedJavaPeer ( GetPayloadValue (managedType), GetPayloadValue (javaType), jniIdentityHashCode, managedObjectHashCode, - GetPayloadValue (runtimeMode)); + GetRuntimeFlavor ()); } - public static void JavaWrapperReleasedDotNetReference ( + internal static void JavaPeerReleasedManagedPeer ( string? managedType, string? javaType, int jniIdentityHashCode, - int managedObjectHashCode, - string? runtimeMode) + int managedObjectHashCode) { - source.JavaWrapperReleasedDotNetReference ( + source.JavaPeerReleasedManagedPeer ( GetPayloadValue (managedType), GetPayloadValue (javaType), jniIdentityHashCode, managedObjectHashCode, - GetPayloadValue (runtimeMode)); + GetRuntimeFlavor ()); } - public static void DotNetObjectOnlyReachableFromJava ( + internal static void ManagedPeerOnlyReachableFromJavaPeer ( string? managedType, string? javaType, int jniIdentityHashCode, int managedObjectHashCode, - string? runtimeMode, int componentIndex, int contextIndex, long contextPointer) { - source.DotNetObjectOnlyReachableFromJava ( + source.ManagedPeerOnlyReachableFromJavaPeer ( GetPayloadValue (managedType), GetPayloadValue (javaType), jniIdentityHashCode, managedObjectHashCode, - GetPayloadValue (runtimeMode), + GetRuntimeFlavor (), componentIndex, contextIndex, contextPointer); } - public static void JavaObjectOnlyReachableFromDotNet ( + internal static void JavaPeerOnlyReachableFromManagedPeer ( string? managedType, string? javaType, int jniIdentityHashCode, int managedObjectHashCode, - string? runtimeMode, int componentIndex, int contextIndex, long contextPointer) { - source.JavaObjectOnlyReachableFromDotNet ( + source.JavaPeerOnlyReachableFromManagedPeer ( GetPayloadValue (managedType), GetPayloadValue (javaType), jniIdentityHashCode, managedObjectHashCode, - GetPayloadValue (runtimeMode), + GetRuntimeFlavor (), componentIndex, contextIndex, contextPointer); @@ -123,49 +133,62 @@ static string GetPayloadValue (string? value) return value ?? UnknownValue; } - [EventSource (Name = ProviderName)] - sealed class InteropEventSourceImplementation : EventSource + static string GetRuntimeFlavor () { - public static class Keywords - { - public const EventKeywords WrapperLifecycle = (EventKeywords) 0x1; - public const EventKeywords Reachability = (EventKeywords) 0x2; + if (IsRuntimeFeatureEnabled ("IsNativeAotRuntime")) { + return "NativeAOT"; } + if (IsRuntimeFeatureEnabled ("IsCoreClrRuntime")) { + return "CoreCLR"; + } + if (IsRuntimeFeatureEnabled ("IsMonoRuntime")) { + return "MonoVM"; + } + return UnknownValue; + } - [Event (1, Level = EventLevel.Informational, Keywords = Keywords.WrapperLifecycle)] - public void DotNetWrapperCreated (string managedType, string javaType, int jniIdentityHashCode, int managedObjectHashCode, string runtimeMode) + static bool IsRuntimeFeatureEnabled (string feature) + { + return AppContext.TryGetSwitch ($"{RuntimeFeatureSwitchPrefix}{feature}", out bool isEnabled) && isEnabled; + } + + [EventSource (Name = ProviderName)] + sealed class InteropEventSourceImplementation : EventSource + { + [Event (1, Level = EventLevel.Informational, Keywords = Keywords.PeerLifecycle)] + public void ManagedPeerCreated (string managedType, string javaType, int jniIdentityHashCode, int managedObjectHashCode, string runtimeFlavor) { - WriteEvent (1, managedType, javaType, jniIdentityHashCode, managedObjectHashCode, runtimeMode); + WriteEvent (1, managedType, javaType, jniIdentityHashCode, managedObjectHashCode, runtimeFlavor); } - [Event (2, Level = EventLevel.Informational, Keywords = Keywords.WrapperLifecycle)] - public void JavaWrapperCreated (string managedType, string javaType, int jniIdentityHashCode, int managedObjectHashCode, string runtimeMode) + [Event (2, Level = EventLevel.Informational, Keywords = Keywords.PeerLifecycle)] + public void JavaPeerCreated (string managedType, string javaType, int jniIdentityHashCode, int managedObjectHashCode, string runtimeFlavor) { - WriteEvent (2, managedType, javaType, jniIdentityHashCode, managedObjectHashCode, runtimeMode); + WriteEvent (2, managedType, javaType, jniIdentityHashCode, managedObjectHashCode, runtimeFlavor); } - [Event (3, Level = EventLevel.Informational, Keywords = Keywords.WrapperLifecycle)] - public void DotNetWrapperReleasedJavaReference (string managedType, string javaType, int jniIdentityHashCode, int managedObjectHashCode, string runtimeMode) + [Event (3, Level = EventLevel.Informational, Keywords = Keywords.PeerLifecycle)] + public void ManagedPeerReleasedJavaPeer (string managedType, string javaType, int jniIdentityHashCode, int managedObjectHashCode, string runtimeFlavor) { - WriteEvent (3, managedType, javaType, jniIdentityHashCode, managedObjectHashCode, runtimeMode); + WriteEvent (3, managedType, javaType, jniIdentityHashCode, managedObjectHashCode, runtimeFlavor); } - [Event (4, Level = EventLevel.Informational, Keywords = Keywords.WrapperLifecycle)] - public void JavaWrapperReleasedDotNetReference (string managedType, string javaType, int jniIdentityHashCode, int managedObjectHashCode, string runtimeMode) + [Event (4, Level = EventLevel.Informational, Keywords = Keywords.PeerLifecycle)] + public void JavaPeerReleasedManagedPeer (string managedType, string javaType, int jniIdentityHashCode, int managedObjectHashCode, string runtimeFlavor) { - WriteEvent (4, managedType, javaType, jniIdentityHashCode, managedObjectHashCode, runtimeMode); + WriteEvent (4, managedType, javaType, jniIdentityHashCode, managedObjectHashCode, runtimeFlavor); } [Event (5, Level = EventLevel.Informational, Keywords = Keywords.Reachability)] - public void DotNetObjectOnlyReachableFromJava (string managedType, string javaType, int jniIdentityHashCode, int managedObjectHashCode, string runtimeMode, int componentIndex, int contextIndex, long contextPointer) + public void ManagedPeerOnlyReachableFromJavaPeer (string managedType, string javaType, int jniIdentityHashCode, int managedObjectHashCode, string runtimeFlavor, int componentIndex, int contextIndex, long contextPointer) { - WriteEvent (5, managedType, javaType, jniIdentityHashCode, managedObjectHashCode, runtimeMode, componentIndex, contextIndex, contextPointer); + WriteEvent (5, managedType, javaType, jniIdentityHashCode, managedObjectHashCode, runtimeFlavor, componentIndex, contextIndex, contextPointer); } [Event (6, Level = EventLevel.Informational, Keywords = Keywords.Reachability)] - public void JavaObjectOnlyReachableFromDotNet (string managedType, string javaType, int jniIdentityHashCode, int managedObjectHashCode, string runtimeMode, int componentIndex, int contextIndex, long contextPointer) + public void JavaPeerOnlyReachableFromManagedPeer (string managedType, string javaType, int jniIdentityHashCode, int managedObjectHashCode, string runtimeFlavor, int componentIndex, int contextIndex, long contextPointer) { - WriteEvent (6, managedType, javaType, jniIdentityHashCode, managedObjectHashCode, runtimeMode, componentIndex, contextIndex, contextPointer); + WriteEvent (6, managedType, javaType, jniIdentityHashCode, managedObjectHashCode, runtimeFlavor, componentIndex, contextIndex, contextPointer); } } } diff --git a/external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs b/external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs index c2677c8f2b6..9f57c3e5657 100644 --- a/external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs +++ b/external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs @@ -116,14 +116,13 @@ public virtual void DisposePeer (IJavaPeerable value) var h = value.PeerReference; if (!h.IsValid) return; - if (InteropEventSource.IsEnabled ()) { + if (RuntimeFeature.IsInteropEventSourceEnabled (InteropEventSource.Keywords.PeerLifecycle)) { var javaType = JniEnvironment.Types.GetJniTypeNameFromInstance (h); - InteropEventSource.DotNetWrapperReleasedJavaReference ( + InteropEventSource.ManagedPeerReleasedJavaPeer ( value.GetType ().FullName, javaType, value.JniIdentityHashCode, - RuntimeHelpers.GetHashCode (value), - Runtime.GetType ().FullName); + RuntimeHelpers.GetHashCode (value)); } DisposePeer (h, value); diff --git a/external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs b/external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs index 9be4ea9b6b8..43de7bab8cd 100644 --- a/external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs +++ b/external/Java.Interop/src/Java.Interop/Java.Interop/JniRuntime.ReflectionJniValueManager.cs @@ -78,8 +78,8 @@ protected override void ConstructPeerCore (IJavaPeerable peer, ref JniObjectRefe peer.SetPeerReference (newRef); peer.SetJniIdentityHashCode (JniSystem.IdentityHashCode (newRef)); - if (!peer.JniManagedPeerState.HasFlag (JniManagedPeerStates.Activatable) && InteropEventSource.IsEnabled ()) { - EmitJavaWrapperCreatedEvent (peer, newRef); + if (RuntimeFeature.IsInteropEventSourceEnabled (InteropEventSource.Keywords.PeerLifecycle)) { + EmitJavaPeerCreatedEvent (peer, newRef); } var o = Runtime.ObjectReferenceManager; @@ -97,32 +97,25 @@ protected override void ConstructPeerCore (IJavaPeerable peer, ref JniObjectRefe } } - void EmitDotNetWrapperCreatedEvent (IJavaPeerable peer) + void EmitManagedPeerCreatedEvent (IJavaPeerable peer) { JniObjectReference reference = peer.PeerReference; var javaType = reference.IsValid ? JniEnvironment.Types.GetJniTypeNameFromInstance (reference) : null; - InteropEventSource.DotNetWrapperCreated ( + InteropEventSource.ManagedPeerCreated ( peer.GetType ().FullName, javaType, peer.JniIdentityHashCode, - RuntimeHelpers.GetHashCode (peer), - GetRuntimeMode ()); + RuntimeHelpers.GetHashCode (peer)); } - void EmitJavaWrapperCreatedEvent (IJavaPeerable peer, JniObjectReference reference) + void EmitJavaPeerCreatedEvent (IJavaPeerable peer, JniObjectReference reference) { var javaType = reference.IsValid ? JniEnvironment.Types.GetJniTypeNameFromInstance (reference) : null; - InteropEventSource.JavaWrapperCreated ( + InteropEventSource.JavaPeerCreated ( peer.GetType ().FullName, javaType, peer.JniIdentityHashCode, - RuntimeHelpers.GetHashCode (peer), - GetRuntimeMode ()); - } - - string GetRuntimeMode () - { - return Runtime.GetType ().FullName ?? "Unknown"; + RuntimeHelpers.GetHashCode (peer)); } // This base method implementation is NOT reachable in trimmable typemap - it is featureswitch guarded @@ -174,8 +167,8 @@ string GetRuntimeMode () JniEnvironment.Types.GetJniTypeNameFromInstance (reference), targetType)); } peer.SetJniManagedPeerState (peer.JniManagedPeerState | JniManagedPeerStates.Replaceable); - if (InteropEventSource.IsEnabled ()) { - EmitDotNetWrapperCreatedEvent (peer); + if (RuntimeFeature.IsInteropEventSourceEnabled (InteropEventSource.Keywords.PeerLifecycle)) { + EmitManagedPeerCreatedEvent (peer); } return peer; } diff --git a/external/Java.Interop/src/Java.Interop/Java.Interop/ManagedPeer.cs b/external/Java.Interop/src/Java.Interop/Java.Interop/ManagedPeer.cs index 6c5e14b5276..d5274e0376c 100644 --- a/external/Java.Interop/src/Java.Interop/Java.Interop/ManagedPeer.cs +++ b/external/Java.Interop/src/Java.Interop/Java.Interop/ManagedPeer.cs @@ -106,14 +106,13 @@ static void Construct ( var typeSig = new JniTypeSignature (JniEnvironment.Types.GetJniTypeNameFromInstance (r_self)); var type = GetTypeFromSignature (runtime.TypeManager, typeSig); - if (InteropEventSource.IsEnabled ()) { + if (RuntimeFeature.IsInteropEventSourceEnabled (InteropEventSource.Keywords.PeerLifecycle)) { var managedObjectHashCode = self != null ? RuntimeHelpers.GetHashCode (self) : 0; - InteropEventSource.JavaWrapperCreated ( + InteropEventSource.JavaPeerCreated ( type.FullName, typeSig.SimpleReference, runtime.ValueManager.GetJniIdentityHashCode (r_self), - managedObjectHashCode, - runtime.GetType ().FullName); + managedObjectHashCode); } if (type.IsGenericTypeDefinition) { diff --git a/external/Java.Interop/src/Java.Interop/Java.Interop/RuntimeFeature.cs b/external/Java.Interop/src/Java.Interop/Java.Interop/RuntimeFeature.cs index afc27a26d32..3a096f64c3a 100644 --- a/external/Java.Interop/src/Java.Interop/Java.Interop/RuntimeFeature.cs +++ b/external/Java.Interop/src/Java.Interop/Java.Interop/RuntimeFeature.cs @@ -1,11 +1,13 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Tracing; namespace Java.Interop { static class RuntimeFeature { const bool ManagedPeerNativeRegistrationEnabledByDefault = true; + const bool InteropEventSourceEnabledByDefault = false; const string FeatureSwitchPrefix = "Java.Interop.RuntimeFeature."; [FeatureSwitchDefinition ($"{FeatureSwitchPrefix}{nameof (ManagedPeerNativeRegistration)}")] @@ -14,5 +16,16 @@ static class RuntimeFeature AppContext.TryGetSwitch ($"{FeatureSwitchPrefix}{nameof (ManagedPeerNativeRegistration)}", out bool isEnabled) ? isEnabled : ManagedPeerNativeRegistrationEnabledByDefault; + + [FeatureSwitchDefinition ($"{FeatureSwitchPrefix}{nameof (InteropEventSource)}")] + internal static bool InteropEventSource { get; } = + AppContext.TryGetSwitch ($"{FeatureSwitchPrefix}{nameof (InteropEventSource)}", out bool isEnabled) + ? isEnabled + : InteropEventSourceEnabledByDefault; + + internal static bool IsInteropEventSourceEnabled (EventKeywords keywords) + { + return InteropEventSource && global::Java.Interop.InteropEventSource.IsEnabled (keywords); + } } } diff --git a/external/Java.Interop/src/Java.Interop/PublicAPI.Unshipped.txt b/external/Java.Interop/src/Java.Interop/PublicAPI.Unshipped.txt index 2a00d8231db..2088a07dc59 100644 --- a/external/Java.Interop/src/Java.Interop/PublicAPI.Unshipped.txt +++ b/external/Java.Interop/src/Java.Interop/PublicAPI.Unshipped.txt @@ -117,11 +117,3 @@ override Java.Interop.JniRuntime.ReflectionJniTypeManager.GetTypesForSimpleRefer override Java.Interop.JniRuntime.ReflectionJniTypeManager.RegisterNativeMembers(Java.Interop.JniType! nativeClass, System.Type! type, string? methods) -> void override Java.Interop.JniRuntime.ReflectionJniTypeManager.RegisterNativeMembers(Java.Interop.JniType! nativeClass, System.Type! type, System.ReadOnlySpan methods) -> void virtual Java.Interop.JniRuntime.ReflectionJniValueManager.TryConstructPeer(Java.Interop.IJavaPeerable! self, ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions options, System.Type! type) -> bool -static Java.Interop.InteropEventSource.DotNetObjectOnlyReachableFromJava(string? managedType, string? javaType, int jniIdentityHashCode, int managedObjectHashCode, string? runtimeMode, int componentIndex, int contextIndex, long contextPointer) -> void -static Java.Interop.InteropEventSource.DotNetWrapperCreated(string? managedType, string? javaType, int jniIdentityHashCode, int managedObjectHashCode, string? runtimeMode) -> void -static Java.Interop.InteropEventSource.DotNetWrapperReleasedJavaReference(string? managedType, string? javaType, int jniIdentityHashCode, int managedObjectHashCode, string? runtimeMode) -> void -static Java.Interop.InteropEventSource.IsEnabled() -> bool -static Java.Interop.InteropEventSource.JavaObjectOnlyReachableFromDotNet(string? managedType, string? javaType, int jniIdentityHashCode, int managedObjectHashCode, string? runtimeMode, int componentIndex, int contextIndex, long contextPointer) -> void -static Java.Interop.InteropEventSource.JavaWrapperCreated(string? managedType, string? javaType, int jniIdentityHashCode, int managedObjectHashCode, string? runtimeMode) -> void -static Java.Interop.InteropEventSource.JavaWrapperReleasedDotNetReference(string? managedType, string? javaType, int jniIdentityHashCode, int managedObjectHashCode, string? runtimeMode) -> void -Java.Interop.InteropEventSource diff --git a/external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/InteropEventSourceTests.cs b/external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/InteropEventSourceTests.cs index b361909ae20..a8795ffd017 100644 --- a/external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/InteropEventSourceTests.cs +++ b/external/Java.Interop/tests/Java.Interop-Tests/Java.Interop/InteropEventSourceTests.cs @@ -18,18 +18,18 @@ public class InteropEventSourceTests public void WrapperLifecycleEvents_HaveExpectedPayload () { using (var listener = new CapturingEventListener ()) { - InteropEventSource.DotNetWrapperCreated ("Managed.Type", "java/type", 1, 2, "CoreCLR"); - InteropEventSource.JavaWrapperCreated ("Managed.Type", "java/type", 3, 4, "CoreCLR"); - InteropEventSource.DotNetWrapperReleasedJavaReference ("Managed.Type", "java/type", 5, 6, "CoreCLR"); - InteropEventSource.JavaWrapperReleasedDotNetReference ("Managed.Type", "java/type", 7, 8, "CoreCLR"); + InteropEventSource.ManagedPeerCreated ("Managed.Type", "java/type", 1, 2); + InteropEventSource.JavaPeerCreated ("Managed.Type", "java/type", 3, 4); + InteropEventSource.ManagedPeerReleasedJavaPeer ("Managed.Type", "java/type", 5, 6); + InteropEventSource.JavaPeerReleasedManagedPeer ("Managed.Type", "java/type", 7, 8); var lifecycleEvents = listener.Events.Where (e => e.EventId is >= 1 and <= 4).ToArray (); Assert.AreEqual (4, lifecycleEvents.Length, "Expected all lifecycle events to be emitted."); - AssertEventPayload (lifecycleEvents [0], "DotNetWrapperCreated", "Managed.Type", "java/type", 1, 2, "CoreCLR"); - AssertEventPayload (lifecycleEvents [1], "JavaWrapperCreated", "Managed.Type", "java/type", 3, 4, "CoreCLR"); - AssertEventPayload (lifecycleEvents [2], "DotNetWrapperReleasedJavaReference", "Managed.Type", "java/type", 5, 6, "CoreCLR"); - AssertEventPayload (lifecycleEvents [3], "JavaWrapperReleasedDotNetReference", "Managed.Type", "java/type", 7, 8, "CoreCLR"); + AssertEventPayload (lifecycleEvents [0], "ManagedPeerCreated", "Managed.Type", "java/type", 1, 2, "Unknown"); + AssertEventPayload (lifecycleEvents [1], "JavaPeerCreated", "Managed.Type", "java/type", 3, 4, "Unknown"); + AssertEventPayload (lifecycleEvents [2], "ManagedPeerReleasedJavaPeer", "Managed.Type", "java/type", 5, 6, "Unknown"); + AssertEventPayload (lifecycleEvents [3], "JavaPeerReleasedManagedPeer", "Managed.Type", "java/type", 7, 8, "Unknown"); } } @@ -37,46 +37,46 @@ public void WrapperLifecycleEvents_HaveExpectedPayload () public void ReachabilityEvents_HaveExpectedPayload () { using (var listener = new CapturingEventListener ()) { - InteropEventSource.DotNetObjectOnlyReachableFromJava ("Managed.Type", "java/type", 11, 12, "NativeAOT", 2, 3, 16); - InteropEventSource.JavaObjectOnlyReachableFromDotNet ("Managed.Type", "java/type", 21, 22, "NativeAOT", 4, 5, 32); + InteropEventSource.ManagedPeerOnlyReachableFromJavaPeer ("Managed.Type", "java/type", 11, 12, 2, 3, 16); + InteropEventSource.JavaPeerOnlyReachableFromManagedPeer ("Managed.Type", "java/type", 21, 22, 4, 5, 32); var reachabilityEvents = listener.Events.Where (e => e.EventId is >= 5 and <= 6).ToArray (); Assert.AreEqual (2, reachabilityEvents.Length, "Expected both reachability events to be emitted."); - AssertReachabilityPayload (reachabilityEvents [0], "DotNetObjectOnlyReachableFromJava", "Managed.Type", "java/type", 11, 12, "NativeAOT", 2, 3, 16L); - AssertReachabilityPayload (reachabilityEvents [1], "JavaObjectOnlyReachableFromDotNet", "Managed.Type", "java/type", 21, 22, "NativeAOT", 4, 5, 32L); + AssertReachabilityPayload (reachabilityEvents [0], "ManagedPeerOnlyReachableFromJavaPeer", "Managed.Type", "java/type", 11, 12, "Unknown", 2, 3, 16L); + AssertReachabilityPayload (reachabilityEvents [1], "JavaPeerOnlyReachableFromManagedPeer", "Managed.Type", "java/type", 21, 22, "Unknown", 4, 5, 32L); } } [Test] public void CallsWithoutListener_DoNotThrow () { - Assert.DoesNotThrow (() => InteropEventSource.DotNetWrapperCreated ("Managed.Type", "java/type", 1, 2, "MonoVM")); - Assert.DoesNotThrow (() => InteropEventSource.JavaWrapperCreated ("Managed.Type", "java/type", 1, 2, "MonoVM")); - Assert.DoesNotThrow (() => InteropEventSource.DotNetWrapperReleasedJavaReference ("Managed.Type", "java/type", 1, 2, "MonoVM")); - Assert.DoesNotThrow (() => InteropEventSource.JavaWrapperReleasedDotNetReference ("Managed.Type", "java/type", 1, 2, "MonoVM")); - Assert.DoesNotThrow (() => InteropEventSource.DotNetObjectOnlyReachableFromJava ("Managed.Type", "java/type", 1, 2, "MonoVM", 1, 1, 1)); - Assert.DoesNotThrow (() => InteropEventSource.JavaObjectOnlyReachableFromDotNet ("Managed.Type", "java/type", 1, 2, "MonoVM", 1, 1, 1)); + Assert.DoesNotThrow (() => InteropEventSource.ManagedPeerCreated ("Managed.Type", "java/type", 1, 2)); + Assert.DoesNotThrow (() => InteropEventSource.JavaPeerCreated ("Managed.Type", "java/type", 1, 2)); + Assert.DoesNotThrow (() => InteropEventSource.ManagedPeerReleasedJavaPeer ("Managed.Type", "java/type", 1, 2)); + Assert.DoesNotThrow (() => InteropEventSource.JavaPeerReleasedManagedPeer ("Managed.Type", "java/type", 1, 2)); + Assert.DoesNotThrow (() => InteropEventSource.ManagedPeerOnlyReachableFromJavaPeer ("Managed.Type", "java/type", 1, 2, 1, 1, 1)); + Assert.DoesNotThrow (() => InteropEventSource.JavaPeerOnlyReachableFromManagedPeer ("Managed.Type", "java/type", 1, 2, 1, 1, 1)); } - static void AssertEventPayload (CapturedEvent captured, string eventName, string managedType, string javaType, int jniHash, int managedHash, string runtimeMode) + static void AssertEventPayload (CapturedEvent captured, string eventName, string managedType, string javaType, int jniHash, int managedHash, string runtimeFlavor) { Assert.AreEqual (eventName, captured.EventName); Assert.AreEqual (managedType, captured.Payload [0]); Assert.AreEqual (javaType, captured.Payload [1]); Assert.AreEqual (jniHash, captured.Payload [2]); Assert.AreEqual (managedHash, captured.Payload [3]); - Assert.AreEqual (runtimeMode, captured.Payload [4]); + Assert.AreEqual (runtimeFlavor, captured.Payload [4]); } - static void AssertReachabilityPayload (CapturedEvent captured, string eventName, string managedType, string javaType, int jniHash, int managedHash, string runtimeMode, int componentIndex, int contextIndex, long contextPointer) + static void AssertReachabilityPayload (CapturedEvent captured, string eventName, string managedType, string javaType, int jniHash, int managedHash, string runtimeFlavor, int componentIndex, int contextIndex, long contextPointer) { Assert.AreEqual (eventName, captured.EventName); Assert.AreEqual (managedType, captured.Payload [0]); Assert.AreEqual (javaType, captured.Payload [1]); Assert.AreEqual (jniHash, captured.Payload [2]); Assert.AreEqual (managedHash, captured.Payload [3]); - Assert.AreEqual (runtimeMode, captured.Payload [4]); + Assert.AreEqual (runtimeFlavor, captured.Payload [4]); Assert.AreEqual (componentIndex, captured.Payload [5]); Assert.AreEqual (contextIndex, captured.Payload [6]); Assert.AreEqual (contextPointer, captured.Payload [7]); diff --git a/src/Mono.Android.Runtime/Mono.Android.Runtime.csproj b/src/Mono.Android.Runtime/Mono.Android.Runtime.csproj index a73aaea64d9..0f7bff622f9 100644 --- a/src/Mono.Android.Runtime/Mono.Android.Runtime.csproj +++ b/src/Mono.Android.Runtime/Mono.Android.Runtime.csproj @@ -70,6 +70,7 @@ + diff --git a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs index b7001a8e906..c7b12bfb920 100644 --- a/src/Mono.Android/Android.Runtime/AndroidRuntime.cs +++ b/src/Mono.Android/Android.Runtime/AndroidRuntime.cs @@ -626,15 +626,14 @@ public override void WaitForGCBridgeProcessing () return null; var peer = Java.Interop.TypeManager.CreateInstance (reference.Handle, JniHandleOwnership.DoNotTransfer, targetType) as IJavaPeerable; - if (peer != null && InteropEventSource.IsEnabled ()) { + if (peer != null && RuntimeFeature.IsInteropEventSourceEnabled (InteropEventSource.Keywords.PeerLifecycle)) { var peerReference = peer.PeerReference; var javaType = peerReference.IsValid ? JniEnvironment.Types.GetJniTypeNameFromInstance (peerReference) : null; - InteropEventSource.DotNetWrapperCreated ( + InteropEventSource.ManagedPeerCreated ( peer.GetType ().FullName, javaType, peer.JniIdentityHashCode, - RuntimeHelpers.GetHashCode (peer), - "MonoVM"); + RuntimeHelpers.GetHashCode (peer)); } JniObjectReference.Dispose (ref reference, options); return peer; diff --git a/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalRegisteredPeers.cs b/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalRegisteredPeers.cs index 494963dcaec..251529cd055 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalRegisteredPeers.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/JavaMarshalRegisteredPeers.cs @@ -183,6 +183,7 @@ public static void RemovePeer (IJavaPeerable value) if (value == null) throw new ArgumentNullException (nameof (value)); + bool removed = false; lock (RegisteredInstances) { int key = value.JniIdentityHashCode; if (!RegisteredInstances.TryGetValue (key, out List? peers)) @@ -192,17 +193,19 @@ public static void RemovePeer (IJavaPeerable value) ReferenceTrackingHandle peer = peers [i]; IJavaPeerable? target = peer.Target; if (ReferenceEquals (value, target)) { - if (InteropEventSource.IsEnabled ()) { - EmitJavaWrapperReleasedDotNetReference (value); - } peers.RemoveAt (i); peer.Dispose (); + removed = true; } GC.KeepAlive (target); } if (peers.Count == 0) RegisteredInstances.Remove (key); } + + if (removed && RuntimeFeature.IsInteropEventSourceEnabled (InteropEventSource.Keywords.PeerLifecycle)) { + EmitJavaPeerReleasedManagedPeer (value); + } } public static void FinalizePeer (IJavaPeerable value) @@ -235,8 +238,8 @@ public static void FinalizePeer (IJavaPeerable value) RuntimeHelpers.GetHashCode (value).ToString ("x", CultureInfo.InvariantCulture), value.GetType ().ToString ()); } - if (InteropEventSource.IsEnabled ()) { - EmitDotNetWrapperReleasedJavaReference (value, h); + if (RuntimeFeature.IsInteropEventSourceEnabled (InteropEventSource.Keywords.PeerLifecycle)) { + EmitManagedPeerReleasedJavaPeer (value, h); } value.SetPeerReference (new JniObjectReference ()); JniObjectReference.Dispose (ref h); @@ -261,32 +264,30 @@ public static List GetSurfacedPeers () } } - static void EmitJavaWrapperReleasedDotNetReference (IJavaPeerable peer) + static void EmitJavaPeerReleasedManagedPeer (IJavaPeerable peer) { JniObjectReference reference = peer.PeerReference; var javaType = reference.IsValid ? JniEnvironment.Types.GetJniTypeNameFromInstance (reference) : null; - InteropEventSource.JavaWrapperReleasedDotNetReference ( + InteropEventSource.JavaPeerReleasedManagedPeer ( peer.GetType ().FullName, javaType, peer.JniIdentityHashCode, - RuntimeHelpers.GetHashCode (peer), - GetRuntimeMode ()); + RuntimeHelpers.GetHashCode (peer)); } - static void EmitDotNetWrapperReleasedJavaReference (IJavaPeerable peer, JniObjectReference reference) + static void EmitManagedPeerReleasedJavaPeer (IJavaPeerable peer, JniObjectReference reference) { var javaType = reference.IsValid ? JniEnvironment.Types.GetJniTypeNameFromInstance (reference) : null; - InteropEventSource.DotNetWrapperReleasedJavaReference ( + InteropEventSource.ManagedPeerReleasedJavaPeer ( peer.GetType ().FullName, javaType, peer.JniIdentityHashCode, - RuntimeHelpers.GetHashCode (peer), - GetRuntimeMode ()); + RuntimeHelpers.GetHashCode (peer)); } unsafe static void EmitReachabilityEventIfEnabled (HandleContext* context, GCHandle handle, int componentIndex, int contextIndex, bool isCollected) { - if (!InteropEventSource.IsEnabled ()) { + if (!RuntimeFeature.IsInteropEventSourceEnabled (InteropEventSource.Keywords.Reachability)) { return; } @@ -302,43 +303,27 @@ unsafe static void EmitReachabilityEventIfEnabled (HandleContext* context, GCHan } if (isCollected) { - InteropEventSource.JavaObjectOnlyReachableFromDotNet ( + InteropEventSource.JavaPeerOnlyReachableFromManagedPeer ( managedType, javaType, context->PeerIdentityHashCode, managedObjectHashCode, - GetRuntimeMode (), componentIndex, contextIndex, (long) (nint) context); return; } - InteropEventSource.DotNetObjectOnlyReachableFromJava ( + InteropEventSource.ManagedPeerOnlyReachableFromJavaPeer ( managedType, javaType, context->PeerIdentityHashCode, managedObjectHashCode, - GetRuntimeMode (), componentIndex, contextIndex, (long) (nint) context); } - static string GetRuntimeMode () - { - if (RuntimeFeature.IsNativeAotRuntime) { - return "NativeAOT"; - } - if (RuntimeFeature.IsCoreClrRuntime) { - return "CoreCLR"; - } - if (RuntimeFeature.IsMonoRuntime) { - return "MonoVM"; - } - return "Unknown"; - } - unsafe struct ReferenceTrackingHandle : IDisposable { WeakReference _weakReference; @@ -534,7 +519,7 @@ void ProcessContext (HandleContext* context, int componentIndex, int contextInde } bool isCollected = context->IsCollected; - if (!isCollected && !InteropEventSource.IsEnabled ()) { + if (!isCollected && !RuntimeFeature.IsInteropEventSourceEnabled (InteropEventSource.Keywords.Reachability)) { return; } diff --git a/src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs b/src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs index f15a79ff6f4..c6f4e5e242c 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Tracing; namespace Microsoft.Android.Runtime; @@ -12,6 +13,7 @@ static class RuntimeFeature const bool StartupHookSupportEnabledByDefault = true; const bool TrimmableTypeMapEnabledByDefault = false; const bool ObjectReferenceLoggingEnabledByDefault = false; + const bool InteropEventSourceEnabledByDefault = false; const string FeatureSwitchPrefix = "Microsoft.Android.Runtime.RuntimeFeature."; const string StartupHookProviderSwitch = "System.StartupHookProvider.IsSupported"; @@ -44,4 +46,13 @@ static class RuntimeFeature [FeatureSwitchDefinition ($"{FeatureSwitchPrefix}{nameof (ObjectReferenceLogging)}")] internal static bool ObjectReferenceLogging { get; } = AppContext.TryGetSwitch ($"{FeatureSwitchPrefix}{nameof (ObjectReferenceLogging)}", out bool isEnabled) ? isEnabled : ObjectReferenceLoggingEnabledByDefault; + + [FeatureSwitchDefinition ($"{FeatureSwitchPrefix}{nameof (InteropEventSource)}")] + internal static bool InteropEventSource { get; } = + AppContext.TryGetSwitch ($"{FeatureSwitchPrefix}{nameof (InteropEventSource)}", out bool isEnabled) ? isEnabled : InteropEventSourceEnabledByDefault; + + internal static bool IsInteropEventSourceEnabled (EventKeywords keywords) + { + return InteropEventSource && global::Microsoft.Android.Runtime.InteropEventSource.IsEnabled (keywords); + } } diff --git a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapValueManager.cs b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapValueManager.cs index 12aa1c236ea..c336f9b6d29 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapValueManager.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMapValueManager.cs @@ -100,14 +100,13 @@ protected override void ConstructPeerCore ( peer.SetPeerReference (newRef); peer.SetJniIdentityHashCode (JniEnvironment.References.GetIdentityHashCode (newRef)); - if (!peer.JniManagedPeerState.HasFlag (JniManagedPeerStates.Activatable) && InteropEventSource.IsEnabled ()) { + if (RuntimeFeature.IsInteropEventSourceEnabled (InteropEventSource.Keywords.PeerLifecycle)) { var javaType = newRef.IsValid ? JniEnvironment.Types.GetJniTypeNameFromInstance (newRef) : null; - InteropEventSource.JavaWrapperCreated ( + InteropEventSource.JavaPeerCreated ( peer.GetType ().FullName, javaType, peer.JniIdentityHashCode, - RuntimeHelpers.GetHashCode (peer), - GetRuntimeMode ()); + RuntimeHelpers.GetHashCode (peer)); } var o = Runtime.ObjectReferenceManager; @@ -141,15 +140,14 @@ protected override void ConstructPeerCore ( var resolvedTargetType = ResolvePeerType (targetType); var peer = TrimmableTypeMap.Instance.CreateInstance (reference.Handle, resolvedTargetType) ?? NotFoundFallback (ref reference, targetType, resolvedTargetType); - if (peer != null && InteropEventSource.IsEnabled ()) { + if (peer != null && RuntimeFeature.IsInteropEventSourceEnabled (InteropEventSource.Keywords.PeerLifecycle)) { var peerReference = peer.PeerReference; var javaType = peerReference.IsValid ? JniEnvironment.Types.GetJniTypeNameFromInstance (peerReference) : null; - InteropEventSource.DotNetWrapperCreated ( + InteropEventSource.ManagedPeerCreated ( peer.GetType ().FullName, javaType, peer.JniIdentityHashCode, - RuntimeHelpers.GetHashCode (peer), - GetRuntimeMode ()); + RuntimeHelpers.GetHashCode (peer)); } return peer; } finally { @@ -239,20 +237,6 @@ static bool IsIncompatibleCast ( return false; } - static string GetRuntimeMode () - { - if (RuntimeFeature.IsNativeAotRuntime) { - return "NativeAOT"; - } - if (RuntimeFeature.IsCoreClrRuntime) { - return "CoreCLR"; - } - if (RuntimeFeature.IsMonoRuntime) { - return "MonoVM"; - } - return "Unknown"; - } - } [return: MaybeNull] diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.RuntimeConfig.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.RuntimeConfig.targets index fe4db586982..09ec0762df4 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.RuntimeConfig.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.RuntimeConfig.targets @@ -16,6 +16,7 @@ See: https://github.com/dotnet/runtime/blob/b13715b6984889a709ba29ea8a1961db469f On non-trimmed (Debug) builds, default to true so that debug.mono.log=gref continues to work as expected. --> <_AndroidEnableObjectReferenceLogging Condition=" '$(_AndroidEnableObjectReferenceLogging)' == '' And '$(PublishTrimmed)' == 'true' ">false <_AndroidEnableObjectReferenceLogging Condition=" '$(_AndroidEnableObjectReferenceLogging)' == '' And '$(PublishTrimmed)' != 'true' ">true + <_AndroidEnableInteropEventSource Condition=" '$(_AndroidEnableInteropEventSource)' == '' ">false <_AndroidEnableDiagnosticCrashReporting Condition=" '$(_AndroidEnableDiagnosticCrashReporting)' == '' ">true @@ -63,6 +64,14 @@ See: https://github.com/dotnet/runtime/blob/b13715b6984889a709ba29ea8a1961db469f Value="$(_AndroidEnableObjectReferenceLogging)" Trim="true" /> + + IL2037 true + <_AndroidEnableInteropEventSource>true