Skip to content

Record high level ASP.NET profiling data at runtime and visualize with speedscope

License

Notifications You must be signed in to change notification settings

bitfaster/Recorder

Repository files navigation

Recorder

Record high level profiling data in ASP.NET Core applications at runtime. Profiles can then be visualized using speedscope.

image

Usage

To use the recorder, register the middleware then request a BlackBox recorder at runtime to instrument different subsystems. The blackbox will store a configurable number of request profiles (default is 16).

Register types

// use default profile naming (root stack frame will have a name like 'GET relative/url?queryparams')
builder.Services.AddRequestRecording();

// for custom naming, implement INomenclator
builder.Services.AddRequestRecording<MyNomenclator>();

Register middleware

At startup, register the middleware:

// always run 
app.UseRequestRecorder();

// profile specific URL
app.UseWhen(context => context.Request.Path.StartsWithSegments("Foo") , appBuilder =>
{
    appBuilder.UseRequestRecorder();
});

Instrument code

For example, to instrument output create an instrumented formatter and decorate the existing formatter classes.

public class InstrumentedOutputFormatter : IOutputFormatter
{
    private readonly IOutputFormatter inner;

    public InstrumentedOutputFormatter(IOutputFormatter inner) { this.inner = inner; }

    public bool CanWriteResult(OutputFormatterCanWriteContext context)
    {
        return inner.CanWriteResult(context);
    }

    public async Task WriteAsync(OutputFormatterWriteContext context)
    {
        // record time spent in this method via Capture
        using var frame = context.HttpContext.RequestServices.RecordStackFrame("OutputFormatter");
        
        await inner.WriteAsync(context);
    }
}
builder.Services
    .AddMvcOptions(options => 
    {
        // wrap all the formatters with instrumentation
        List<IOutputFormatter> newFormatters = new List<IOutputFormatter>(options.OutputFormatters.Count);

        foreach (var f in options.OutputFormatters)
        {
            newFormatters.Add(new InstrumentedOutputFormatter(f));
        }

        options.OutputFormatters.Clear();

        foreach (var nf in newFormatters)
        {
            options.OutputFormatters.Add(nf);
        }
    });

Add a profiling controller

Add a controller to integrate with speedscope easily at runtime by sending HTTP GET to https://localhost/profile. This controller will first redirect the caller to speedscope with a parameterized profile URL. When speedscope requests the profile data, detect it via the origin header and return the profile.

builder.Services.AddCors(options =>
{
    options.AddPolicy(ProfileDataController.CorsPolicyName,
    builder =>
    {
        builder
            .WithOrigins(@"https://www.speedscope.app")
            .AllowAnyMethod()
            .AllowAnyHeader()
            .AllowCredentials();
    });
});

// ...

app.UseCors(ProfileDataController.CorsPolicyName);
[ApiController]
[Route("[controller]")]
public class ProfileController : ControllerBase
{
    public const string CorsPolicyName = "allowSpeedscope";

    [EnableCors(CorsPolicyName)]
    public async Task<IActionResult> Get()
    {
        if (this.HttpContext.Request.Headers.TryGetValue("Origin", out var origin) && origin[0] == "https://www.speedscope.app")
        {
            var memoryStream = new MemoryStream();

            using (var speedscopeWriter = new SpeedscopeWriter(memoryStream))
            {
                speedscopeWriter.WritePreAmble();

                foreach (var request in BlackBox.History)
                {
                    speedscopeWriter.WriteEvent(request);
                }

                speedscopeWriter.Flush();
            }
            memoryStream.Position = 0;

            var fileResult = File(memoryStream, "application/json", "profile.json");
            return fileResult;
        }

        if (!BlackBox.HasHistory)
        {
            return NotFound("No profiles have been recorded");
        }

        return Redirect($"https://speedscope.app#profileURL=https://{this.HttpContext.Request.Host}/Profile");
    }
}

About

Record high level ASP.NET profiling data at runtime and visualize with speedscope

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages