BenchmarkDotNet icon indicating copy to clipboard operation
BenchmarkDotNet copied to clipboard

Suspect `-p ETW` is not enabling the CLR rundown provider

Open AndyAyersMS opened this issue 2 years ago • 3 comments

When there's a benchmark that spends substantial amounts of time in R2R code, neither PerfView or my own ETL parsing can resolve CPU samples down to the method level.

For example https://github.com/dotnet/runtime/issues/84264#issuecomment-1521994085.

Seems like we might see similar things on desktop with NGEN, though I have not tried this.

https://github.com/microsoft/perfview/blob/564309807cb280823377db4e6fc24a1aeae34a90/src/TraceEvent/Parsers/ClrTraceEventParser.cs#L13406-L13449

AndyAyersMS avatar Apr 26 '23 02:04 AndyAyersMS

BDN is also not enabling the tiered compilation settings event, so PerfView is confused and thinks TieredCompilation is not enabled:

image

AndyAyersMS avatar Apr 28 '23 16:04 AndyAyersMS

@brianrob wonder if you could take a look sometime. I can't get method load events for R2R methods no matter what I try.

I have cases where I want to see these events even if there are no samples to attribute to them (eg to prove a particular sequence of rejits was performed by tiered compilation).

Here's a sample app where the ETW config BDN will use is easy to modify:

using System;
using System.Buffers;
using BenchmarkDotNet;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Diagnostics.Windows;
using BenchmarkDotNet.Configs;

using BenchmarkDotNet.Diagnosers;
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.Parsers;
using Microsoft.Diagnostics.Tracing.Parsers.Clr;
using Microsoft.Diagnostics.Tracing.Session;

namespace System.Buffers.Tests
{

    [GenericTypeArguments(typeof(byte))] // value type
    [GenericTypeArguments(typeof(object))] // reference type
    public class NonStandardArrayPoolTests<T>
    {
        private readonly ArrayPool<T> _createdPool = ArrayPool<T>.Create();

        private const int Iterations = 100_000;

        [Params(64)]
        public int RentalSize;

        [Params(false, true)]
        public bool UseSharedPool { get; set; }

        public ArrayPool<T> Pool => UseSharedPool ? ArrayPool<T>.Shared : _createdPool;

        [Benchmark(OperationsPerInvoke = Iterations)]
        public void RentNoReturn()
        {
            ArrayPool<T> pool = Pool;
            for (int i = 0; i < Iterations; i++)
            {
                pool.Rent(RentalSize);
            }
        }
    }
}

class Program
{

    static void Main(string[] args)
    {
        var p = new (Guid providerGuid, TraceEventLevel providerLevel, ulong keywords, TraceEventProviderOptions options)[]
        {
            // following values come from xunit-performance, were selected by the .NET Runtime Team
            (ClrTraceEventParser.ProviderGuid, TraceEventLevel.Verbose,
                (ulong) (ClrTraceEventParser.Keywords.Exception
                            | ClrTraceEventParser.Keywords.StartEnumeration
                            | ClrTraceEventParser.Keywords.Jit
                            | ClrTraceEventParser.Keywords.Loader
                            | ClrTraceEventParser.Keywords.NGen),
                new TraceEventProviderOptions { StacksEnabled = false }), // stacks are too expensive for our purposes
                            // following values come from xunit-performance, were selected by the .NET Runtime Team
            // (
            //    ClrRundownTraceEventParser.ProviderGuid,
            // // g,
            // TraceEventLevel.Verbose,
            //     (ulong) (ClrRundownTraceEventParser.Keywords.StartEnumeration
            //                 | ClrRundownTraceEventParser.Keywords.Loader
            //                 // | ClrRundownTraceEventParser.Keywords.ForceEndRundown   
            //                 | ClrRundownTraceEventParser.Keywords.Jit                 
            //                 | ClrRundownTraceEventParser.Keywords.NGen),
            //     new TraceEventProviderOptions { StacksEnabled = false }) // stacks are too expensive for our purposes
        };

        var ep = new EtwProfilerConfig(performExtraBenchmarksRun: false, providers: p);

        BenchmarkSwitcher
            .FromAssembly(typeof(Program).Assembly)
            .Run(args,
                DefaultConfig.Instance.AddDiagnoser(new EtwProfiler(ep))); // HERE
    }
}

There should be quite a few R2R method loads here, in particular I am looking for various SpinWait methods.

AndyAyersMS avatar Jul 18 '25 23:07 AndyAyersMS

@AndyAyersMS it looks like you're doing this right. A Method/LoadVerbose can be emitted during R2R method restore. However, it looks like this either never worked or was broken at some point. Debugging it, the call to MethodAndStartAddressToEECodeInfoPointer returns NULL. I suspect the issue is that the code hasn't been fully loaded yet, as I am able to get the R2R events to fire properly at rundown.

If this is something that would be useful to you, we probably need to file a bug in https://github.com/dotnet/runtime.

brianrob avatar Jul 28 '25 17:07 brianrob