Skip to content

AllocationSampled (EventID 303, .NET 10+) has no typed schema in ClrTraceEventParser #2385

@baal2000

Description

@baal2000

Problem

AllocationSampled (EventID 303) was added to the CLR in .NET 10 via dotnet/runtime#104955. ClrTraceEventParser.cs.base has no entry for it — EventID 303 is entirely absent from the parser, which currently covers events up to approximately EventID 190.

Because CLR runtime events do not embed ETW manifest blocks in EventPipe .nettrace files (unlike EventSource events), the Dynamic parser cannot synthesise a schema at runtime. All field accessors fail — and critically, PayloadValue fails silently: it neither throws nor returns null but returns an opaque sentinel string, making it impossible to distinguish a missing schema from a valid value:

source.Dynamic.All += data =>
{
    if (data.ProviderName != "Microsoft-Windows-DotNETRuntime") return;
    if ((int)data.ID != 303) return;

    // All of these yield nothing useful:
    Console.WriteLine(data.PayloadNames?.Length);     // 0  ← empty array, no fields
    Console.WriteLine(data.PayloadByName("TypeName")); // null
    Console.WriteLine(data.PayloadByName("ObjectSize")); // null
    Console.WriteLine(data.PayloadValue(0));           // "<<<EXCEPTION_DURING_VALUE_LOOKUP IndexOutOfRangeException>>>"
};

source.Clr.All never fires for EventID 303 — the event is not routed through ClrTraceEventParser at all.

The event carries the following payload fields (from ClrEtwAll.man):

# Field Type
0 AllocationKind win:UInt32 (0=SOH, 1=LOH, 2=POH)
1 ClrInstanceID win:UInt16
2 TypeID win:Pointer
3 TypeName win:UnicodeString
4 Address win:Pointer
5 ObjectSize win:UInt64
6 SampledByteOffset win:UInt64

Expected behaviour

ClrTraceEventParser should expose a typed event handler (e.g. Clr.AllocationSampled) backed by an AllocationSampledTraceData class, analogous to GCAllocationTickTraceData (EventID 10). PayloadNames should return ["AllocationKind", "ClrInstanceID", "TypeID", "TypeName", "Address", "ObjectSize", "SampledByteOffset"] and all PayloadByName/PayloadValue calls should return correctly typed values.


Actual behaviour

PayloadNames returns an empty string[] (length 0, not null). PayloadByName(...) returns null. PayloadValue(0) returns the sentinel string <<<EXCEPTION_DURING_VALUE_LOOKUP IndexOutOfRangeException>>> — TraceEvent catches the internal IndexOutOfRangeException and surfaces it as a string rather than rethrowing. The only way to extract any data is raw byte parsing via TraceEvent.EventData() using the binary layout reverse-engineered from the dotnet/runtime reference consumer.


Workaround

Parse the raw payload from data.EventData() using the known binary layout. Pointer size is read from data.PointerSize.

AllocationKind(4) + ClrInstanceID(2) + TypeID(ptr) + TypeName(UTF-16LE+\0\0)
    + Address(ptr) + ObjectSize(8) + SampledByteOffset(8)

Full implementation: TryParseAllocationSampled in the repro project.


Environment

Microsoft.Diagnostics.Tracing.TraceEvent 3.1.30 (latest)
.NET runtime 10.0+ — event is never emitted on .NET 9 or earlier
Provider Microsoft-Windows-DotNETRuntime
Keyword AllocationSamplingKeyword = 0x80000000000

Repro

Self-contained .NET 10 console project: https://github.com/baal2000/AllocationSampledRepro

git clone https://github.com/baal2000/AllocationSampledRepro
cd AllocationSampledRepro
dotnet run

The project instruments itself via DiagnosticsClient with AllocationSamplingKeyword, allocates 200 000 objects to guarantee sampled events, then parses the resulting trace twice — once via the native TraceEvent API (broken), once via EventData() raw bytes (workaround).

Actual console output

CI-verified on GitHub-hosted ubuntu-latest, .NET 10.0.5, TraceEvent 3.1.30 — job log:

══ Native TraceEvent API (broken) ═════════════════════════════════════
  PayloadNames:                <empty array>
  PayloadByName("TypeName"):   <null>
  PayloadByName("ObjectSize"): <null>
  PayloadValue(0):             <<<EXCEPTION_DURING_VALUE_LOOKUP IndexOutOfRangeException>>>
  source.Clr.All hits for EventID 303:  0  (0 = not routed through ClrTraceEventParser)
  source.Dynamic.All hits for EventID 303: 88

══ Raw EventData() workaround (works) ══════════════════════════════════
  [1] TypeName=InvokeFunc_RefArgs  ObjectSize=64 bytes
  [2] TypeName=System.Reflection.RuntimeParameterInfo  ObjectSize=88 bytes
  [3] TypeName=System.String  ObjectSize=104 bytes
  [4] TypeName=System.Reflection.ParameterInfo[]  ObjectSize=120 bytes
  [5] TypeName=System.Reflection.RuntimeParameterInfo  ObjectSize=88 bytes
  Total events decoded via raw bytes: 100% (88 / 88)

CI: PASS
  [1] Issue confirmed   — PayloadNames empty, Clr.All: 0 hits for EventID 303
  [2] Workaround confirmed — raw bytes decoded 100% of sampled events

References

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions