Skip to content

Support independently enabling hosting event counters and metrics #50565

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/Hosting/Hosting/src/Internal/HostingApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,8 @@ public Activity? Activity

public long StartTimestamp { get; set; }
internal bool HasDiagnosticListener { get; set; }
public bool EventLogOrMetricsEnabled { get; set; }
public bool MetricsEnabled { get; set; }
public bool EventLogEnabled { get; set; }

internal HttpActivityFeature? HttpActivityFeature;
internal HttpMetricsTagsFeature? MetricsTagsFeature;
Expand All @@ -159,7 +160,8 @@ public void Reset()

StartTimestamp = 0;
HasDiagnosticListener = false;
EventLogOrMetricsEnabled = false;
MetricsEnabled = false;
EventLogEnabled = false;
MetricsTagsFeature?.TagsList.Clear();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,27 @@ public void BeginRequest(HttpContext httpContext, HostingApplication.Context con
{
long startTimestamp = 0;

if (_eventSource.IsEnabled() || _metrics.IsEnabled())
if (_metrics.IsEnabled())
{
context.EventLogOrMetricsEnabled = true;
context.MetricsEnabled = true;
context.MetricsTagsFeature ??= new HttpMetricsTagsFeature();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This gets allocated per request when metrics are on?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s cached on the context

httpContext.Features.Set<IHttpMetricsTagsFeature>(context.MetricsTagsFeature);

startTimestamp = Stopwatch.GetTimestamp();

// To keep the hot path short we defer logging in this function to non-inlines
RecordRequestStartMetrics(httpContext);
}

if (_eventSource.IsEnabled())
{
context.EventLogEnabled = true;

if (startTimestamp == 0)
{
startTimestamp = Stopwatch.GetTimestamp();
}

// To keep the hot path short we defer logging in this function to non-inlines
RecordRequestStartEventLog(httpContext);
}
Expand Down Expand Up @@ -135,7 +148,7 @@ public void RequestEnd(HttpContext httpContext, Exception? exception, HostingApp
// Non-inline
LogRequestFinished(context, startTimestamp, currentTimestamp);

if (context.EventLogOrMetricsEnabled)
if (context.MetricsEnabled)
{
var route = httpContext.GetEndpoint()?.Metadata.GetMetadata<IRouteDiagnosticsMetadata>()?.Route;
var customTags = context.MetricsTagsFeature?.TagsList;
Expand Down Expand Up @@ -197,7 +210,7 @@ public void RequestEnd(HttpContext httpContext, Exception? exception, HostingApp
StopActivity(httpContext, activity, context.HasDiagnosticListener);
}

if (context.EventLogOrMetricsEnabled)
if (context.EventLogEnabled)
{
if (exception != null)
{
Expand All @@ -219,7 +232,7 @@ public void RequestEnd(HttpContext httpContext, Exception? exception, HostingApp
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ContextDisposed(HostingApplication.Context context)
{
if (context.EventLogOrMetricsEnabled)
if (context.EventLogEnabled)
{
_eventSource.RequestStop();
}
Expand Down Expand Up @@ -352,10 +365,15 @@ internal UnhandledExceptionData(HttpContext httpContext, long timestamp, Excepti
[MethodImpl(MethodImplOptions.NoInlining)]
private void RecordRequestStartEventLog(HttpContext httpContext)
{
_metrics.RequestStart(httpContext.Request.IsHttps, httpContext.Request.Scheme, httpContext.Request.Method, httpContext.Request.Host);
_eventSource.RequestStart(httpContext.Request.Method, httpContext.Request.Path);
}

[MethodImpl(MethodImplOptions.NoInlining)]
private void RecordRequestStartMetrics(HttpContext httpContext)
{
_metrics.RequestStart(httpContext.Request.IsHttps, httpContext.Request.Scheme, httpContext.Request.Method, httpContext.Request.Host);
}

[MethodImpl(MethodImplOptions.NoInlining)]
private Activity? StartActivity(HttpContext httpContext, bool loggingEnabled, bool diagnosticListenerActivityCreationEnabled, out bool hasDiagnosticListener)
{
Expand Down
50 changes: 50 additions & 0 deletions src/Hosting/Hosting/test/HostingApplicationDiagnosticsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,56 @@ public async Task EventCountersAndMetricsValues()
m => Assert.True(m.Value > 0));
}

[Fact]
public void EventCountersEnabled()
{
// Arrange
var hostingEventSource = new HostingEventSource(Guid.NewGuid().ToString());

var eventListener = new TestCounterListener(new[]
{
"requests-per-second",
"total-requests",
"current-requests",
"failed-requests"
});

eventListener.EnableEvents(hostingEventSource, EventLevel.Informational, EventKeywords.None,
new Dictionary<string, string>
{
{ "EventCounterIntervalSec", "1" }
});

var testMeterFactory = new TestMeterFactory();

// Act
var hostingApplication = CreateApplication(out var features, eventSource: hostingEventSource, meterFactory: testMeterFactory);
var context = hostingApplication.CreateContext(features);

// Assert
Assert.True(context.EventLogEnabled);
Assert.False(context.MetricsEnabled);
}

[Fact]
public void MetricsEnabled()
{
// Arrange
var hostingEventSource = new HostingEventSource(Guid.NewGuid().ToString());

var testMeterFactory = new TestMeterFactory();
using var activeRequestsCollector = new MetricCollector<long>(testMeterFactory, HostingMetrics.MeterName, "http.server.active_requests");
using var requestDurationCollector = new MetricCollector<double>(testMeterFactory, HostingMetrics.MeterName, "http.server.request.duration");

// Act
var hostingApplication = CreateApplication(out var features, eventSource: hostingEventSource, meterFactory: testMeterFactory);
var context = hostingApplication.CreateContext(features);

// Assert
Assert.True(context.MetricsEnabled);
Assert.False(context.EventLogEnabled);
}

[Fact]
public void DisposeContextDoesNotThrowWhenContextScopeIsNull()
{
Expand Down