Skip to content

Commit 7d0c273

Browse files
authored
Add metrics to ASP.NET Core (#46834)
1 parent 06ff899 commit 7d0c273

File tree

90 files changed

+2384
-316
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

90 files changed

+2384
-316
lines changed

src/Hosting/Abstractions/src/Microsoft.AspNetCore.Hosting.Abstractions.csproj

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,28 @@
1111
<IsTrimmable>true</IsTrimmable>
1212
</PropertyGroup>
1313

14+
<ItemGroup>
15+
<Compile Include="$(SharedSourceRoot)Metrics\**\*.cs" LinkBase="Metrics" />
16+
</ItemGroup>
17+
18+
<!-- Temporary hack to make prototype Metrics DI integration types available -->
19+
<!-- TODO: Remove when Metrics DI intergration package is available https://github.com/dotnet/aspnetcore/issues/47618 -->
20+
<ItemGroup>
21+
<InternalsVisibleTo Include="Microsoft.AspNetCore.Hosting" />
22+
<InternalsVisibleTo Include="Microsoft.AspNetCore.Hosting.Tests" />
23+
<InternalsVisibleTo Include="Microsoft.AspNetCore.Server.Kestrel.Core" />
24+
<InternalsVisibleTo Include="Microsoft.AspNetCore.Server.Kestrel.Core.Tests" />
25+
<InternalsVisibleTo Include="Microsoft.AspNetCore.Server.Kestrel.Tests" />
26+
<InternalsVisibleTo Include="InMemory.FunctionalTests" />
27+
<InternalsVisibleTo Include="Sockets.BindTests" />
28+
<InternalsVisibleTo Include="Sockets.FunctionalTests" />
29+
<InternalsVisibleTo Include="Microsoft.AspNetCore.Server.Kestrel.Microbenchmarks" />
30+
<InternalsVisibleTo Include="Microsoft.AspNetCore.Http.Connections" />
31+
<InternalsVisibleTo Include="Microsoft.AspNetCore.Http.Connections.Tests" />
32+
<InternalsVisibleTo Include="Microsoft.AspNetCore.SignalR" />
33+
<InternalsVisibleTo Include="Microsoft.AspNetCore.Diagnostics.Tests" />
34+
</ItemGroup>
35+
1436
<ItemGroup>
1537
<Reference Include="Microsoft.AspNetCore.Hosting.Server.Abstractions" />
1638
<Reference Include="Microsoft.AspNetCore.Http.Abstractions" />

src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,12 @@ public GenericWebHostBuilder(IHostBuilder builder, WebHostBuilderOptions options
6262
#pragma warning restore CS0618 // Type or member is obsolete
6363

6464
services.Configure<GenericWebHostServiceOptions>(options =>
65-
{
66-
// Set the options
67-
options.WebHostOptions = webHostOptions;
68-
// Store and forward any startup errors
69-
options.HostingStartupExceptions = _hostingStartupErrors;
70-
});
65+
{
66+
// Set the options
67+
options.WebHostOptions = webHostOptions;
68+
// Store and forward any startup errors
69+
options.HostingStartupExceptions = _hostingStartupErrors;
70+
});
7171

7272
// REVIEW: This is bad since we don't own this type. Anybody could add one of these and it would mess things up
7373
// We need to flow this differently
@@ -80,6 +80,9 @@ public GenericWebHostBuilder(IHostBuilder builder, WebHostBuilderOptions options
8080
services.TryAddScoped<IMiddlewareFactory, MiddlewareFactory>();
8181
services.TryAddSingleton<IApplicationBuilderFactory, ApplicationBuilderFactory>();
8282

83+
services.AddMetrics();
84+
services.TryAddSingleton<HostingMetrics>();
85+
8386
// IMPORTANT: This needs to run *before* direct calls on the builder (like UseStartup)
8487
_hostingStartupWebHostBuilder?.ConfigureServices(webhostContext, services);
8588

src/Hosting/Hosting/src/GenericHost/GenericWebHostService.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ public GenericWebHostService(IOptions<GenericWebHostServiceOptions> options,
2626
IApplicationBuilderFactory applicationBuilderFactory,
2727
IEnumerable<IStartupFilter> startupFilters,
2828
IConfiguration configuration,
29-
IWebHostEnvironment hostingEnvironment)
29+
IWebHostEnvironment hostingEnvironment,
30+
HostingMetrics hostingMetrics)
3031
{
3132
Options = options.Value;
3233
Server = server;
@@ -40,6 +41,7 @@ public GenericWebHostService(IOptions<GenericWebHostServiceOptions> options,
4041
StartupFilters = startupFilters;
4142
Configuration = configuration;
4243
HostingEnvironment = hostingEnvironment;
44+
HostingMetrics = hostingMetrics;
4345
}
4446

4547
public GenericWebHostServiceOptions Options { get; }
@@ -55,6 +57,7 @@ public GenericWebHostService(IOptions<GenericWebHostServiceOptions> options,
5557
public IEnumerable<IStartupFilter> StartupFilters { get; }
5658
public IConfiguration Configuration { get; }
5759
public IWebHostEnvironment HostingEnvironment { get; }
60+
public HostingMetrics HostingMetrics { get; }
5861

5962
public async Task StartAsync(CancellationToken cancellationToken)
6063
{
@@ -153,7 +156,7 @@ static string ExpandPorts(string ports, string scheme)
153156
application = ErrorPageBuilder.BuildErrorPageApplication(HostingEnvironment.ContentRootFileProvider, Logger, showDetailedErrors, ex);
154157
}
155158

156-
var httpApplication = new HostingApplication(application, Logger, DiagnosticListener, ActivitySource, Propagator, HttpContextFactory);
159+
var httpApplication = new HostingApplication(application, Logger, DiagnosticListener, ActivitySource, Propagator, HttpContextFactory, HostingEventSource.Log, HostingMetrics);
157160

158161
await Server.StartAsync(httpApplication, cancellationToken);
159162
HostingEventSource.Log.ServerReady();

src/Hosting/Hosting/src/GenericHost/SlimWebHostBuilder.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ public SlimWebHostBuilder(IHostBuilder builder, WebHostBuilderOptions options)
5353
services.TryAddSingleton<IHttpContextFactory, DefaultHttpContextFactory>();
5454
services.TryAddScoped<IMiddlewareFactory, MiddlewareFactory>();
5555
services.TryAddSingleton<IApplicationBuilderFactory, ApplicationBuilderFactory>();
56+
57+
services.AddMetrics();
58+
services.TryAddSingleton<HostingMetrics>();
5659
});
5760
}
5861

src/Hosting/Hosting/src/Internal/HostingApplication.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@ public HostingApplication(
2323
DiagnosticListener diagnosticSource,
2424
ActivitySource activitySource,
2525
DistributedContextPropagator propagator,
26-
IHttpContextFactory httpContextFactory)
26+
IHttpContextFactory httpContextFactory,
27+
HostingEventSource eventSource,
28+
HostingMetrics metrics)
2729
{
2830
_application = application;
29-
_diagnostics = new HostingApplicationDiagnostics(logger, diagnosticSource, activitySource, propagator);
31+
_diagnostics = new HostingApplicationDiagnostics(logger, diagnosticSource, activitySource, propagator, eventSource, metrics);
3032
if (httpContextFactory is DefaultHttpContextFactory factory)
3133
{
3234
_defaultHttpContextFactory = factory;
@@ -110,7 +112,7 @@ public void DisposeContext(Context context, Exception? exception)
110112
_httpContextFactory!.Dispose(httpContext);
111113
}
112114

113-
HostingApplicationDiagnostics.ContextDisposed(context);
115+
_diagnostics.ContextDisposed(context);
114116

115117
// Reset the context as it may be pooled
116118
context.Reset();
@@ -139,9 +141,10 @@ public Activity? Activity
139141

140142
public long StartTimestamp { get; set; }
141143
internal bool HasDiagnosticListener { get; set; }
142-
public bool EventLogEnabled { get; set; }
144+
public bool EventLogOrMetricsEnabled { get; set; }
143145

144146
internal IHttpActivityFeature? HttpActivityFeature;
147+
internal HttpMetricsTagsFeature? MetricsTagsFeature;
145148

146149
public void Reset()
147150
{
@@ -153,7 +156,8 @@ public void Reset()
153156

154157
StartTimestamp = 0;
155158
HasDiagnosticListener = false;
156-
EventLogEnabled = false;
159+
EventLogOrMetricsEnabled = false;
160+
MetricsTagsFeature?.Tags.Clear();
157161
}
158162
}
159163
}

src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@
88
using System.Runtime.CompilerServices;
99
using Microsoft.AspNetCore.Http;
1010
using Microsoft.AspNetCore.Http.Features;
11+
using Microsoft.AspNetCore.Http.Metadata;
1112
using Microsoft.Extensions.Logging;
1213

1314
namespace Microsoft.AspNetCore.Hosting;
1415

1516
internal sealed class HostingApplicationDiagnostics
1617
{
17-
private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency;
18-
1918
// internal so it can be used in tests
2019
internal const string ActivityName = "Microsoft.AspNetCore.Hosting.HttpRequestIn";
2120
private const string ActivityStartKey = ActivityName + ".Start";
@@ -28,28 +27,46 @@ internal sealed class HostingApplicationDiagnostics
2827
private readonly ActivitySource _activitySource;
2928
private readonly DiagnosticListener _diagnosticListener;
3029
private readonly DistributedContextPropagator _propagator;
30+
private readonly HostingEventSource _eventSource;
31+
private readonly HostingMetrics _metrics;
3132
private readonly ILogger _logger;
3233

3334
public HostingApplicationDiagnostics(
3435
ILogger logger,
3536
DiagnosticListener diagnosticListener,
3637
ActivitySource activitySource,
37-
DistributedContextPropagator propagator)
38+
DistributedContextPropagator propagator,
39+
HostingEventSource eventSource,
40+
HostingMetrics metrics)
3841
{
3942
_logger = logger;
4043
_diagnosticListener = diagnosticListener;
4144
_activitySource = activitySource;
4245
_propagator = propagator;
46+
_eventSource = eventSource;
47+
_metrics = metrics;
4348
}
4449

4550
[MethodImpl(MethodImplOptions.AggressiveInlining)]
4651
public void BeginRequest(HttpContext httpContext, HostingApplication.Context context)
4752
{
4853
long startTimestamp = 0;
4954

50-
if (HostingEventSource.Log.IsEnabled())
55+
if (_eventSource.IsEnabled() || _metrics.IsEnabled())
5156
{
52-
context.EventLogEnabled = true;
57+
context.EventLogOrMetricsEnabled = true;
58+
if (httpContext.Features.Get<IHttpMetricsTagsFeature>() is HttpMetricsTagsFeature feature)
59+
{
60+
context.MetricsTagsFeature = feature;
61+
}
62+
else
63+
{
64+
context.MetricsTagsFeature ??= new HttpMetricsTagsFeature();
65+
httpContext.Features.Set<IHttpMetricsTagsFeature>(context.MetricsTagsFeature);
66+
}
67+
68+
startTimestamp = Stopwatch.GetTimestamp();
69+
5370
// To keep the hot path short we defer logging in this function to non-inlines
5471
RecordRequestStartEventLog(httpContext);
5572
}
@@ -80,7 +97,11 @@ public void BeginRequest(HttpContext httpContext, HostingApplication.Context con
8097
{
8198
if (_diagnosticListener.IsEnabled(DeprecatedDiagnosticsBeginRequestKey))
8299
{
83-
startTimestamp = Stopwatch.GetTimestamp();
100+
if (startTimestamp == 0)
101+
{
102+
startTimestamp = Stopwatch.GetTimestamp();
103+
}
104+
84105
RecordBeginRequestDiagnostics(httpContext, startTimestamp);
85106
}
86107
}
@@ -121,6 +142,25 @@ public void RequestEnd(HttpContext httpContext, Exception? exception, HostingApp
121142
currentTimestamp = Stopwatch.GetTimestamp();
122143
// Non-inline
123144
LogRequestFinished(context, startTimestamp, currentTimestamp);
145+
146+
if (context.EventLogOrMetricsEnabled)
147+
{
148+
var route = httpContext.GetEndpoint()?.Metadata.GetMetadata<IRouteDiagnosticsMetadata>()?.Route;
149+
var customTags = context.MetricsTagsFeature?.TagsList;
150+
151+
_metrics.RequestEnd(
152+
httpContext.Request.Protocol,
153+
httpContext.Request.IsHttps,
154+
httpContext.Request.Scheme,
155+
httpContext.Request.Method,
156+
httpContext.Request.Host,
157+
route,
158+
httpContext.Response.StatusCode,
159+
exception,
160+
customTags,
161+
startTimestamp,
162+
currentTimestamp);
163+
}
124164
}
125165

126166
if (_diagnosticListener.IsEnabled())
@@ -159,18 +199,18 @@ public void RequestEnd(HttpContext httpContext, Exception? exception, HostingApp
159199
StopActivity(httpContext, activity, context.HasDiagnosticListener);
160200
}
161201

162-
if (context.EventLogEnabled)
202+
if (context.EventLogOrMetricsEnabled)
163203
{
164204
if (exception != null)
165205
{
166206
// Non-inline
167-
HostingEventSource.Log.UnhandledException();
207+
_eventSource.UnhandledException();
168208
}
169209

170210
// Count 500 as failed requests
171211
if (httpContext.Response.StatusCode >= 500)
172212
{
173-
HostingEventSource.Log.RequestFailed();
213+
_eventSource.RequestFailed();
174214
}
175215
}
176216

@@ -179,12 +219,11 @@ public void RequestEnd(HttpContext httpContext, Exception? exception, HostingApp
179219
}
180220

181221
[MethodImpl(MethodImplOptions.AggressiveInlining)]
182-
public static void ContextDisposed(HostingApplication.Context context)
222+
public void ContextDisposed(HostingApplication.Context context)
183223
{
184-
if (context.EventLogEnabled)
224+
if (context.EventLogOrMetricsEnabled)
185225
{
186-
// Non-inline
187-
HostingEventSource.Log.RequestStop();
226+
_eventSource.RequestStop();
188227
}
189228
}
190229

@@ -211,7 +250,7 @@ private void LogRequestFinished(HostingApplication.Context context, long startTi
211250
// so check if we logged the start event
212251
if (context.StartLog != null)
213252
{
214-
var elapsed = new TimeSpan((long)(TimestampToTicks * (currentTimestamp - startTimestamp)));
253+
var elapsed = Stopwatch.GetElapsedTime(startTimestamp, currentTimestamp);
215254

216255
_logger.Log(
217256
logLevel: LogLevel.Information,
@@ -302,9 +341,10 @@ internal UnhandledExceptionData(HttpContext httpContext, long timestamp, Excepti
302341
}
303342

304343
[MethodImpl(MethodImplOptions.NoInlining)]
305-
private static void RecordRequestStartEventLog(HttpContext httpContext)
344+
private void RecordRequestStartEventLog(HttpContext httpContext)
306345
{
307-
HostingEventSource.Log.RequestStart(httpContext.Request.Method, httpContext.Request.Path);
346+
_metrics.RequestStart(httpContext.Request.IsHttps, httpContext.Request.Scheme, httpContext.Request.Method, httpContext.Request.Host);
347+
_eventSource.RequestStart(httpContext.Request.Method, httpContext.Request.Path);
308348
}
309349

310350
[MethodImpl(MethodImplOptions.NoInlining)]

src/Hosting/Hosting/src/Internal/HostingEventSource.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ internal sealed class HostingEventSource : EventSource
2020
private long _failedRequests;
2121

2222
internal HostingEventSource()
23-
: this("Microsoft.AspNetCore.Hosting")
23+
: base("Microsoft.AspNetCore.Hosting", EventSourceSettings.EtwManifestEventFormat)
2424
{
2525
}
2626

@@ -78,6 +78,7 @@ public void ServerReady()
7878
WriteEvent(6);
7979
}
8080

81+
[NonEvent]
8182
internal void RequestFailed()
8283
{
8384
Interlocked.Increment(ref _failedRequests);

0 commit comments

Comments
 (0)