Skip to content

[Android/iOS AOT] System.Diagnostics.Metrics: Meter.s_allMeters remains empty, breaking all metrics collection #124226

@LosDavidos

Description

@LosDavidos

Description

Problem Description

System.Diagnostics.Metrics completely fails to register any meters when AOT/linking is enabled on .NET MAUI Android and iOS. The static field Meter.s_allMeters remains empty, causing MeterListener.InstrumentPublished callbacks to never fire.

This seems to be BCL issue, not an OpenTelemetry SDK issue. Any code relying on System.Diagnostics.Metrics will fail silently under AOT/linking on both Android and iOS.

Tested and confirmed on both platforms:

  • Android Release build (AOT enabled): Metrics completely broken, no logs
  • iOS Release build (Linking enabled): Metrics completely broken, no logs
  • Debug builds on both platforms: Metrics work perfectly

Reproduction Steps

Code

// Create a meter
var meter = new Meter("Mobile", "1.0.0");

// Create instruments
var counter = meter.CreateCounter<long>("app.launch.total", "1", "Total app launches");
var histogram = meter.CreateHistogram<double>("photo.capture.time", "ms", "Photo capture time");

// Record values
counter.Add(1);
histogram.Record(1234.5);

// Set up listener
var listener = new MeterListener();
listener.InstrumentPublished = (instrument, listener) =>
{
    Console.WriteLine($"✅ INSTRUMENT PUBLISHED: {instrument.Name}");
    listener.EnableMeasurementEvents(instrument);
};
listener.SetMeasurementEventCallback<long>((instrument, measurement, tags, state) =>
{
    Console.WriteLine($"📊 MEASUREMENT: {instrument.Name} = {measurement}");
});
listener.Start();

Expected behavior

Expected Behavior (Debug build)

Diagnostic logs show successful registration:

[OTEL-DIAGNOSTICS] Registered Meters via Reflection
Static Fields in Meter: 2
  Field: s_allMeters
    Type: List`1
    Meter Count: 5
    Meter #1:
      Name: System.Runtime
      Instruments Count: 19
    Meter #2:
      Name: Mobile
      Instruments Count: 10
        Instrument #1: photo.taken.total
        ...

[OTEL-DIAGNOSTICS] Manual MeterListener Test
✅ INSTRUMENT PUBLISHED!
  Meter: Mobile
  Instrument: photo.taken.total
  Type: Counter`1
  ✅ Enabled measurements for photo.taken.total

✅ INSTRUMENT PUBLISHED!
  Meter: Mobile
  Instrument: app.launch.total
  Type: Counter`1
  ✅ Enabled measurements for app.launch.total

Listener started!
Instruments found: 39

[CONSOLE-EXPORTER] TestExporter Export called at 13:35:01
[CONSOLE-EXPORTER] ✅ Found 1 metrics:

Metric: app.launch.total
  Description: Total number of application launches
  Unit: 1
  Type: LongSum
  Point #1:
    Tags: device.manufacturer=samsung, device.model=SM-A057G, device.os=15, device.platform=Android, system_language=pl-PL
    Value (Sum): 1
    Start: 12:34:53
    End: 12:35:01
  Total Points: 1

Actual behavior

Actual Behavior (Release + AOT)

Diagnostic logs show complete failure:

[OTEL-DIAGNOSTICS] Registered Meters via Reflection
Static Fields in Meter: 2
  Field: s_allMeters
    Type: List`1
    Meter Count: 0
    ⚠️ NO METERS REGISTERED!
  Field: <IsSupported>k__BackingField
    Type: Boolean

[OTEL-DIAGNOSTICS] Manual MeterListener Test
Starting listener...
Listener started!
⚠️ NO INSTRUMENTS PUBLISHED TO LISTENER!
This means MeterListener.InstrumentPublished is NOT being called.
This is the root cause of the AOT issue.
Instruments found: 0

[CONSOLE-EXPORTER] TestExporter Export called at 13:28:48
[CONSOLE-EXPORTER] Batch size: 0
[CONSOLE-EXPORTER] ⚠️ EMPTY BATCH - No metrics collected!

Regression?

No response

Known Workarounds

No response

Configuration

Environment

  • .NET Version: 10.0 (net10.0-android, net10.0-ios)
  • Platform:
    • Android (tested on Samsung SM-A057G, Android 15)
    • iOS (tested on iPhone 16 pro, iOS 26.2.1)
  • AOT Settings (Android Release):
    <RunAOTCompilation>true</RunAOTCompilation>
    <AndroidAotMode>Hybrid</AndroidAotMode>
    <AndroidLinkMode>SdkOnly</AndroidLinkMode>
  • iOS Release Settings:
    <MtouchLink>SdkOnly</MtouchLink>
    <MtouchExtraArgs>--optimize=register-protocols</MtouchExtraArgs>
    <UseLlvm>false</UseLlvm>
    <Optimize>true</Optimize>
  • OpenTelemetry Version: 1.14.0
  • Build Configuration: Release

Other information

Root Cause Analysis

When new Meter("Mobile", "1.0.0") is called, the meter instance is created but never registered in the static Meter.s_allMeters list. This appears to be caused by AOT compilation breaking the reflection-based registration mechanism in the Meter constructor or static initializer.

Sequence of Events

Working (Debug):

  1. new Meter("Mobile", "1.0.0") → Creates meter instance
  2. Meter registers itself in Meter.s_allMeters static field
  3. MeterListener.Start() → Iterates s_allMeters and fires InstrumentPublished callbacks
  4. Measurements are collected and exported

Broken (Release + AOT):

  1. new Meter("Mobile", "1.0.0") → Creates meter instance
  2. ❌ Meter fails to register in Meter.s_allMeters (remains empty)
  3. MeterListener.Start() → Iterates empty list, no callbacks fired
  4. ❌ No measurements collected (empty batches exported)

Diagnostic Evidence

We created a manual console exporter that directly subscribes to MeterListener to verify the root cause:

public class ConsoleTestMetricExporter : BaseExporter<Metric>
{
    public override ExportResult Export(in Batch<Metric> batch)
    {
        var count = 0;
        foreach (var metric in batch)
        {
            count++;
            Console.WriteLine($"Metric: {metric.Name}");
        }
        
        if (count == 0)
        {
            Console.WriteLine("[CONSOLE-EXPORTER] ⚠️ EMPTY BATCH - No metrics collected!");
        }
        
        return ExportResult.Success;
    }
}

Result: The exporter is called correctly, but the batch is always empty in Release+AOT builds because MeterListener never receives any instruments.

Additional Information

  • LinkerConfig.xml with <assembly fullname="System.Diagnostics.DiagnosticSource" preserve="All" /> does not help
  • rd.xml preservation directives do not help
  • The issue occurs even with AndroidLinkMode=None
  • The issue is specific to AOT compilation; trimming alone does not cause the problem

Related Issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions