Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions external/Java.Interop/Documentation/EventPipeInteropEvents.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Java.Interop EventPipe interop events

The .NET ↔ Java interop layer emits EventPipe events through two providers:

- `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 | `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

All events contain:

- `managedType` (`string`)
- `javaType` (`string`)
- `jniIdentityHashCode` (`int`)
- `managedObjectHashCode` (`int`)
- `runtimeFlavor` (`string`): `MonoVM`, `CoreCLR`, `NativeAOT`, or `Unknown`

Reachability events (`5`, `6`) additionally contain:

- `componentIndex` (`int`)
- `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
<PropertyGroup>
<_AndroidEnableInteropEventSource>true</_AndroidEnableInteropEventSource>
</PropertyGroup>
```

## Collecting events

Use `dotnet-trace` to capture both providers:

```bash
dotnet-trace collect --process-id <pid> --providers Java.Interop:0x3:4,Microsoft.Android.Runtime:0x3:4
```

`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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
#nullable enable

using System;
using System.Diagnostics.Tracing;

#if INSIDE_MONO_ANDROID_RUNTIME
namespace Microsoft.Android.Runtime
#else
namespace Java.Interop
#endif
{
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 const EventKeywords PeerLifecycleKeyword = (EventKeywords) 0x1;
internal const EventKeywords ReachabilityKeyword = (EventKeywords) 0x2;

static readonly InteropEventSourceImplementation source = new InteropEventSourceImplementation ();

internal static bool IsEnabled (EventKeywords keywords)
{
return source.IsEnabled (EventLevel.Informational, keywords);
}

internal static void ManagedPeerCreated (
string? managedType,
string? javaType,
int jniIdentityHashCode,
int managedObjectHashCode)
{
source.ManagedPeerCreated (
GetPayloadValue (managedType),
GetPayloadValue (javaType),
jniIdentityHashCode,
managedObjectHashCode,
GetRuntimeFlavor ());
}

internal static void JavaPeerCreated (
string? managedType,
string? javaType,
int jniIdentityHashCode,
int managedObjectHashCode)
{
source.JavaPeerCreated (
GetPayloadValue (managedType),
GetPayloadValue (javaType),
jniIdentityHashCode,
managedObjectHashCode,
GetRuntimeFlavor ());
}

internal static void ManagedPeerReleasedJavaPeer (
string? managedType,
string? javaType,
int jniIdentityHashCode,
int managedObjectHashCode)
{
source.ManagedPeerReleasedJavaPeer (
GetPayloadValue (managedType),
GetPayloadValue (javaType),
jniIdentityHashCode,
managedObjectHashCode,
GetRuntimeFlavor ());
}

internal static void JavaPeerReleasedManagedPeer (
string? managedType,
string? javaType,
int jniIdentityHashCode,
int managedObjectHashCode)
{
source.JavaPeerReleasedManagedPeer (
GetPayloadValue (managedType),
GetPayloadValue (javaType),
jniIdentityHashCode,
managedObjectHashCode,
GetRuntimeFlavor ());
}

internal static void ManagedPeerOnlyReachableFromJavaPeer (
string? managedType,
string? javaType,
int jniIdentityHashCode,
int managedObjectHashCode,
int componentIndex,
int contextIndex,
long contextPointer)
{
source.ManagedPeerOnlyReachableFromJavaPeer (
GetPayloadValue (managedType),
GetPayloadValue (javaType),
jniIdentityHashCode,
managedObjectHashCode,
GetRuntimeFlavor (),
componentIndex,
contextIndex,
contextPointer);
}

internal static void JavaPeerOnlyReachableFromManagedPeer (
string? managedType,
string? javaType,
int jniIdentityHashCode,
int managedObjectHashCode,
int componentIndex,
int contextIndex,
long contextPointer)
{
source.JavaPeerOnlyReachableFromManagedPeer (
GetPayloadValue (managedType),
GetPayloadValue (javaType),
jniIdentityHashCode,
managedObjectHashCode,
GetRuntimeFlavor (),
componentIndex,
contextIndex,
contextPointer);
}

static string GetPayloadValue (string? value)
{
return value ?? UnknownValue;
}

static string GetRuntimeFlavor ()
{
if (IsRuntimeFeatureEnabled ("IsNativeAotRuntime")) {
return "NativeAOT";
}
if (IsRuntimeFeatureEnabled ("IsCoreClrRuntime")) {
return "CoreCLR";
}
if (IsRuntimeFeatureEnabled ("IsMonoRuntime")) {
return "MonoVM";
}
return UnknownValue;
}

static bool IsRuntimeFeatureEnabled (string feature)
{
return AppContext.TryGetSwitch ($"{RuntimeFeatureSwitchPrefix}{feature}", out bool isEnabled) && isEnabled;
}

[EventSource (Name = ProviderName)]
sealed class InteropEventSourceImplementation : EventSource
{
public static class Keywords
{
public const EventKeywords PeerLifecycle = PeerLifecycleKeyword;
public const EventKeywords Reachability = ReachabilityKeyword;
}

[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, runtimeFlavor);
}

[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, runtimeFlavor);
}

[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, runtimeFlavor);
}

[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, runtimeFlavor);
}

[Event (5, Level = EventLevel.Informational, Keywords = Keywords.Reachability)]
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, runtimeFlavor, componentIndex, contextIndex, contextPointer);
}

[Event (6, Level = EventLevel.Informational, Keywords = Keywords.Reachability)]
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, runtimeFlavor, componentIndex, contextIndex, contextPointer);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ public virtual void DisposePeer (IJavaPeerable value)
var h = value.PeerReference;
if (!h.IsValid)
return;
if (RuntimeFeature.IsInteropEventSourceEnabled (InteropEventSource.PeerLifecycleKeyword)) {
var javaType = JniEnvironment.Types.GetJniTypeNameFromInstance (h);
InteropEventSource.ManagedPeerReleasedJavaPeer (
value.GetType ().FullName,
javaType,
value.JniIdentityHashCode,
RuntimeHelpers.GetHashCode (value));
}

DisposePeer (h, value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ protected override void ConstructPeerCore (IJavaPeerable peer, ref JniObjectRefe

peer.SetPeerReference (newRef);
peer.SetJniIdentityHashCode (JniSystem.IdentityHashCode (newRef));
if (RuntimeFeature.IsInteropEventSourceEnabled (InteropEventSource.PeerLifecycleKeyword)) {
EmitJavaPeerCreatedEvent (peer, newRef);
}

var o = Runtime.ObjectReferenceManager;
if (o.LogGlobalReferenceMessages) {
Expand All @@ -94,6 +97,27 @@ protected override void ConstructPeerCore (IJavaPeerable peer, ref JniObjectRefe
}
}

void EmitManagedPeerCreatedEvent (IJavaPeerable peer)
{
JniObjectReference reference = peer.PeerReference;
var javaType = reference.IsValid ? JniEnvironment.Types.GetJniTypeNameFromInstance (reference) : null;
InteropEventSource.ManagedPeerCreated (
peer.GetType ().FullName,
javaType,
peer.JniIdentityHashCode,
RuntimeHelpers.GetHashCode (peer));
}

void EmitJavaPeerCreatedEvent (IJavaPeerable peer, JniObjectReference reference)
{
var javaType = reference.IsValid ? JniEnvironment.Types.GetJniTypeNameFromInstance (reference) : null;
InteropEventSource.JavaPeerCreated (
peer.GetType ().FullName,
javaType,
peer.JniIdentityHashCode,
RuntimeHelpers.GetHashCode (peer));
}

// This base method implementation is NOT reachable in trimmable typemap - it is featureswitch guarded
public override IJavaPeerable? CreatePeer (
ref JniObjectReference reference,
Expand Down Expand Up @@ -143,6 +167,9 @@ protected override void ConstructPeerCore (IJavaPeerable peer, ref JniObjectRefe
JniEnvironment.Types.GetJniTypeNameFromInstance (reference), targetType));
}
peer.SetJniManagedPeerState (peer.JniManagedPeerState | JniManagedPeerStates.Replaceable);
if (RuntimeFeature.IsInteropEventSourceEnabled (InteropEventSource.PeerLifecycleKeyword)) {
EmitManagedPeerCreatedEvent (peer);
}
return peer;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -105,6 +106,14 @@ static void Construct (

var typeSig = new JniTypeSignature (JniEnvironment.Types.GetJniTypeNameFromInstance (r_self));
var type = GetTypeFromSignature (runtime.TypeManager, typeSig);
if (RuntimeFeature.IsInteropEventSourceEnabled (InteropEventSource.PeerLifecycleKeyword)) {
var managedObjectHashCode = self != null ? RuntimeHelpers.GetHashCode (self) : 0;
InteropEventSource.JavaPeerCreated (
type.FullName,
typeSig.SimpleReference,
runtime.ValueManager.GetJniIdentityHashCode (r_self),
managedObjectHashCode);
}

if (type.IsGenericTypeDefinition) {
throw new NotSupportedException (
Expand Down
Original file line number Diff line number Diff line change
@@ -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)}")]
Expand All @@ -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);
}
}
}
Loading
Loading