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
69 changes: 69 additions & 0 deletions external/Java.Interop/Documentation/EventPipeInteropEvents.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Java.Interop EventPipe interop events

The .NET ↔ Java interop layer emits diagnostics through three providers:

- `Java.Interop` emits events from the shared Java interop layer.
- `Microsoft.Android.Runtime` emits events from the Android runtime layer.
- `Microsoft.Android.Runtime.InteropMetrics` emits aggregate GC-bridge EventCounters 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.

## Collecting aggregate bridge metrics

Use `dotnet-counters` to enable the aggregate GC-bridge metrics without turning on the fine-grained lifecycle/reachability events:

```bash
dotnet-counters monitor --process-id <pid> --counters Microsoft.Android.Runtime.InteropMetrics
```

The aggregate counter provider emits:

- `managed-objects-only-reachable-from-java`
- `java-objects-only-reachable-from-managed`

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,140 @@
#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 InteropCounterEventSource
{
#if INSIDE_MONO_ANDROID_RUNTIME
internal const string ProviderName = "Microsoft.Android.Runtime.InteropMetrics";
#else
internal const string ProviderName = "Java.Interop.InteropMetrics";
#endif

internal const string ManagedObjectsOnlyReachableFromJavaCounterName = "managed-objects-only-reachable-from-java";
internal const string JavaObjectsOnlyReachableFromManagedCounterName = "java-objects-only-reachable-from-managed";

static readonly InteropCounterEventSourceImplementation source = new ();

internal static bool AreBridgeProcessingCountersEnabled ()
{
return source.BridgeProcessingCountersEnabled;
}

internal static void ReportBridgeProcessingMetrics (
int managedObjectsOnlyReachableFromJavaCount,
int javaObjectsOnlyReachableFromManagedCount)
{
source.ReportBridgeProcessingMetrics (
managedObjectsOnlyReachableFromJavaCount,
javaObjectsOnlyReachableFromManagedCount);
}

[EventSource (Name = ProviderName)]
sealed class InteropCounterEventSourceImplementation : EventSource
{
const string EventCounterIntervalSecArgumentName = "EventCounterIntervalSec";

EventCounter? managedObjectsOnlyReachableFromJavaCounter;
EventCounter? javaObjectsOnlyReachableFromManagedCounter;
readonly object countersLock = new ();

volatile bool bridgeProcessingCountersEnabled;

internal bool BridgeProcessingCountersEnabled => bridgeProcessingCountersEnabled;

[NonEvent]
protected override void OnEventCommand (EventCommandEventArgs command)
{
base.OnEventCommand (command);

if (command.Command == EventCommand.Disable) {
lock (countersLock) {
bridgeProcessingCountersEnabled = false;
DisposeBridgeProcessingCounters ();
}
return;
}

if (command.Command != EventCommand.Enable || command.Arguments == null || !command.Arguments.ContainsKey (EventCounterIntervalSecArgumentName)) {
return;
}

lock (countersLock) {
EnsureBridgeProcessingCountersInitialized ();
bridgeProcessingCountersEnabled = true;
}
}

[NonEvent]
internal void ReportBridgeProcessingMetrics (
int managedObjectsOnlyReachableFromJavaCount,
int javaObjectsOnlyReachableFromManagedCount)
{
lock (countersLock) {
if (!bridgeProcessingCountersEnabled) {
return;
}

GetManagedObjectsOnlyReachableFromJavaCounter ().WriteMetric (managedObjectsOnlyReachableFromJavaCount);
GetJavaObjectsOnlyReachableFromManagedCounter ().WriteMetric (javaObjectsOnlyReachableFromManagedCount);
}
}

[NonEvent]
void EnsureBridgeProcessingCountersInitialized ()
{
managedObjectsOnlyReachableFromJavaCounter ??= CreateCounter (
ManagedObjectsOnlyReachableFromJavaCounterName,
".NET objects only reachable from Java");
javaObjectsOnlyReachableFromManagedCounter ??= CreateCounter (
JavaObjectsOnlyReachableFromManagedCounterName,
"Java objects only reachable from .NET");
}

[NonEvent]
EventCounter CreateCounter (string name, string displayName)
{
return new EventCounter (name, this) {
DisplayName = displayName,
DisplayUnits = "count",
};
}

[NonEvent]
EventCounter GetManagedObjectsOnlyReachableFromJavaCounter ()
{
if (managedObjectsOnlyReachableFromJavaCounter == null) {
throw new InvalidOperationException ($"{ManagedObjectsOnlyReachableFromJavaCounterName} counter was not initialized.");
}

return managedObjectsOnlyReachableFromJavaCounter;
}

[NonEvent]
EventCounter GetJavaObjectsOnlyReachableFromManagedCounter ()
{
if (javaObjectsOnlyReachableFromManagedCounter == null) {
throw new InvalidOperationException ($"{JavaObjectsOnlyReachableFromManagedCounterName} counter was not initialized.");
}

return javaObjectsOnlyReachableFromManagedCounter;
}

[NonEvent]
void DisposeBridgeProcessingCounters ()
{
managedObjectsOnlyReachableFromJavaCounter?.Dispose ();
managedObjectsOnlyReachableFromJavaCounter = null;
javaObjectsOnlyReachableFromManagedCounter?.Dispose ();
javaObjectsOnlyReachableFromManagedCounter = null;
}
}
}
}
Loading
Loading