diff --git a/TestAspNetCoreInstrumentationPerf/Controllers/WeatherForecastController.cs b/TestAspNetCoreInstrumentationPerf/Controllers/WeatherForecastController.cs new file mode 100644 index 0000000000..6bd2d62b1d --- /dev/null +++ b/TestAspNetCoreInstrumentationPerf/Controllers/WeatherForecastController.cs @@ -0,0 +1,31 @@ +using Microsoft.AspNetCore.Mvc; + +namespace TestAspNetCoreInstrumentationPerf.Controllers; +[ApiController] +[Route("[controller]")] +public class WeatherForecastController : ControllerBase +{ + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet(Name = "GetWeatherForecast")] + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } +} diff --git a/TestAspNetCoreInstrumentationPerf/DiagnosticSourceListener.cs b/TestAspNetCoreInstrumentationPerf/DiagnosticSourceListener.cs new file mode 100644 index 0000000000..e4ad839f39 --- /dev/null +++ b/TestAspNetCoreInstrumentationPerf/DiagnosticSourceListener.cs @@ -0,0 +1,18 @@ +namespace TestAspNetCoreInstrumentationPerf; + +public class DiagnosticSourceListener : IObserver> +{ + public void OnCompleted() + { + } + + public void OnError(Exception error) + { + } + + public void OnNext(KeyValuePair value) + { + // do nothing + // Measure how much perf is impacted by simply subscribing to diagnostic source. + } +} \ No newline at end of file diff --git a/TestAspNetCoreInstrumentationPerf/DiagnosticSourceObserver.cs b/TestAspNetCoreInstrumentationPerf/DiagnosticSourceObserver.cs new file mode 100644 index 0000000000..e515801097 --- /dev/null +++ b/TestAspNetCoreInstrumentationPerf/DiagnosticSourceObserver.cs @@ -0,0 +1,35 @@ +using System.Diagnostics; + +namespace TestAspNetCoreInstrumentationPerf +{ + internal sealed class DiagnosticSourceSubscriber : IObserver + { + private static readonly HashSet DiagnosticSourceEvents = new() + { + "Microsoft.AspNetCore.Hosting.HttpRequestIn", + "Microsoft.AspNetCore.Hosting.HttpRequestIn.Start", + "Microsoft.AspNetCore.Hosting.HttpRequestIn.Stop", + "Microsoft.AspNetCore.Diagnostics.UnhandledException", + "Microsoft.AspNetCore.Hosting.UnhandledException", + }; + + private readonly Func isEnabled = (eventName, _, _) + => DiagnosticSourceEvents.Contains(eventName); + + public void OnCompleted() + { + } + + public void OnError(Exception error) + { + } + + public void OnNext(DiagnosticListener value) + { + if (value.Name == "Microsoft.AspNetCore") + { + _ = value.Subscribe(new DiagnosticSourceListener(), isEnabled); + } + } + } +} diff --git a/TestAspNetCoreInstrumentationPerf/InstrumentationOptions.cs b/TestAspNetCoreInstrumentationPerf/InstrumentationOptions.cs new file mode 100644 index 0000000000..135f3e84e5 --- /dev/null +++ b/TestAspNetCoreInstrumentationPerf/InstrumentationOptions.cs @@ -0,0 +1,10 @@ +namespace TestAspNetCoreInstrumentationPerf; + +public class InstrumentationOptions +{ + public bool EnableTraceInstrumentation { get; set; } + + public bool EnableMetricInstrumentation { get; set; } + + public bool EnableDiagnosticSourceSubscription { get; set; } +} diff --git a/TestAspNetCoreInstrumentationPerf/MyExporter.cs b/TestAspNetCoreInstrumentationPerf/MyExporter.cs new file mode 100644 index 0000000000..aa26271e24 --- /dev/null +++ b/TestAspNetCoreInstrumentationPerf/MyExporter.cs @@ -0,0 +1,45 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Text; +using OpenTelemetry; + +internal class MyExporter : BaseExporter + where T : class +{ + private readonly string name; + + public MyExporter(string name = "MyExporter") + { + this.name = name; + } + + public override ExportResult Export(in Batch batch) + { + return ExportResult.Success; + } + + protected override bool OnShutdown(int timeoutMilliseconds) + { + Console.WriteLine($"{this.name}.OnShutdown(timeoutMilliseconds={timeoutMilliseconds})"); + return true; + } + + protected override void Dispose(bool disposing) + { + Console.WriteLine($"{this.name}.Dispose({disposing})"); + } +} diff --git a/TestAspNetCoreInstrumentationPerf/Program.cs b/TestAspNetCoreInstrumentationPerf/Program.cs new file mode 100644 index 0000000000..85dade38c8 --- /dev/null +++ b/TestAspNetCoreInstrumentationPerf/Program.cs @@ -0,0 +1,60 @@ +using System.Diagnostics; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; +using TestAspNetCoreInstrumentationPerf; + +var builder = WebApplication.CreateBuilder(args); + +var instrumentationOptions = new InstrumentationOptions(); +builder.Configuration.GetSection("InstrumentationOptions").Bind(instrumentationOptions); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +Console.WriteLine("EnableMetricInstrumentation: " + instrumentationOptions.EnableMetricInstrumentation); +Console.WriteLine("EnableTraceInstrumentation: " + instrumentationOptions.EnableTraceInstrumentation); +Console.WriteLine("EnableDiagnosticSourceSubscription: " + instrumentationOptions.EnableDiagnosticSourceSubscription); + +if (instrumentationOptions.EnableMetricInstrumentation) +{ + builder.Services.AddOpenTelemetry().WithMetrics(builder => builder.AddAspNetCoreInstrumentation().AddReader(new PeriodicExportingMetricReader(new MyExporter("TestExporter")))); +} + +if (instrumentationOptions.EnableTraceInstrumentation) +{ + builder.Services.AddOpenTelemetry().WithTracing(builder => builder.AddAspNetCoreInstrumentation()); +} + +if (instrumentationOptions.EnableDiagnosticSourceSubscription) +{ + ActivitySource.AddActivityListener(new ActivityListener + { + ShouldListenTo = source => source.Name == "Microsoft.AspNetCore", + Sample = (ref ActivityCreationOptions options) => ActivitySamplingResult.AllDataAndRecorded, + ActivityStarted = activity => { }, + ActivityStopped = activity => { }, + }); + + DiagnosticListener.AllListeners.Subscribe(new DiagnosticSourceSubscriber()); +} + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/TestAspNetCoreInstrumentationPerf/TestAspNetCoreInstrumentationPerf.csproj b/TestAspNetCoreInstrumentationPerf/TestAspNetCoreInstrumentationPerf.csproj new file mode 100644 index 0000000000..5ebcd40e9e --- /dev/null +++ b/TestAspNetCoreInstrumentationPerf/TestAspNetCoreInstrumentationPerf.csproj @@ -0,0 +1,16 @@ + + + + net8.0 + enable + enable + true + + + + + + + + + diff --git a/TestAspNetCoreInstrumentationPerf/TestAspNetCoreInstrumentationPerf.http b/TestAspNetCoreInstrumentationPerf/TestAspNetCoreInstrumentationPerf.http new file mode 100644 index 0000000000..2f0f3e91e9 --- /dev/null +++ b/TestAspNetCoreInstrumentationPerf/TestAspNetCoreInstrumentationPerf.http @@ -0,0 +1,6 @@ +@TestAspNetCoreInstrumentationPerf_HostAddress = http://localhost:5203 + +GET {{TestAspNetCoreInstrumentationPerf_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/TestAspNetCoreInstrumentationPerf/WeatherForecast.cs b/TestAspNetCoreInstrumentationPerf/WeatherForecast.cs new file mode 100644 index 0000000000..3d41e65d0d --- /dev/null +++ b/TestAspNetCoreInstrumentationPerf/WeatherForecast.cs @@ -0,0 +1,12 @@ +namespace TestAspNetCoreInstrumentationPerf; + +public class WeatherForecast +{ + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } +} diff --git a/TestAspNetCoreInstrumentationPerf/appsettings.Development.json b/TestAspNetCoreInstrumentationPerf/appsettings.Development.json new file mode 100644 index 0000000000..0c208ae918 --- /dev/null +++ b/TestAspNetCoreInstrumentationPerf/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/TestAspNetCoreInstrumentationPerf/appsettings.json b/TestAspNetCoreInstrumentationPerf/appsettings.json new file mode 100644 index 0000000000..1466473c94 --- /dev/null +++ b/TestAspNetCoreInstrumentationPerf/appsettings.json @@ -0,0 +1,14 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "InstrumentationOptions": { + "EnableTraceInstrumentation": false, + "EnableMetricInstrumentation": false, + "EnableDiagnosticSourceSubscription" : false + }, + "AllowedHosts": "*" +}