Recorder 0.9.1

dotnet add package Recorder --version 0.9.1
NuGet\Install-Package Recorder -Version 0.9.1
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Recorder" Version="0.9.1" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Recorder --version 0.9.1
#r "nuget: Recorder, 0.9.1"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Recorder as a Cake Addin
#addin nuget:?package=Recorder&version=0.9.1

// Install Recorder as a Cake Tool
#tool nuget:?package=Recorder&version=0.9.1

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");
    }
}
Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.9.1 248 3/10/2023
0.9.0 190 3/3/2023