Skip to content
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

HttpClientHandler request metrics #87319

Merged
merged 31 commits into from
Jul 13, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ec16299
initial implementation of request metrics
antonfirsov Jun 9, 2023
2bacea0
simplify error handling code and add comments
antonfirsov Jun 9, 2023
74597a4
typo
antonfirsov Jun 9, 2023
12af084
fix Http.Unit.Tests build failure
antonfirsov Jun 9, 2023
528f3b1
wip
antonfirsov Jun 22, 2023
0d97a7d
Merge branch 'main' into MetricsHandler-02
antonfirsov Jun 22, 2023
4fcfee6
callback-based enrichment API with reftype context
antonfirsov Jun 22, 2023
bd461c8
Protect against re-reentrancy
antonfirsov Jun 22, 2023
3c80a0b
use shared counters with the shared meter
antonfirsov Jun 22, 2023
7406c46
do not cache instruments already cached by Meter
antonfirsov Jun 23, 2023
9da9485
use [ThreadStatic] instead of ThreadLocal
antonfirsov Jun 23, 2023
9251264
Merge branch 'main' into MetricsHandler-05
antonfirsov Jun 27, 2023
2a90c07
sync with main
antonfirsov Jun 27, 2023
869bd34
review feedback
antonfirsov Jun 27, 2023
1d43c3e
use FrozenDictionary for caching status codes
antonfirsov Jun 27, 2023
d4eadb1
Merge branch 'main' into MetricsHandler-02
antonfirsov Jul 9, 2023
493e7d8
implement final design
antonfirsov Jul 9, 2023
d049d4f
fixup merge
antonfirsov Jul 9, 2023
6d4cd18
rename "contextCache" to "pool"
antonfirsov Jul 9, 2023
9f4dd8e
comments
antonfirsov Jul 9, 2023
df6189e
Simplify status code cache
antonfirsov Jul 11, 2023
bbc38af
Merge branch 'MetricsHandler-02' of https://github.com/antonfirsov/ru…
antonfirsov Jul 11, 2023
cd5e4a4
review feedback
antonfirsov Jul 11, 2023
62f87b2
move instrument names to constants in tests
antonfirsov Jul 11, 2023
6283c24
harden HttpMetricsTest_DefaultMeter
antonfirsov Jul 11, 2023
6b77359
remove unnecessary reference to System.Collections.Immutable
antonfirsov Jul 11, 2023
f61c85b
lazy-init s_boxedStatusCodes
antonfirsov Jul 12, 2023
9eb314e
oops
antonfirsov Jul 12, 2023
8e35e19
simplify HttpMetricsTest_DefaultMeter.RequestDuration_Success_Recorde…
antonfirsov Jul 12, 2023
1e7453f
move the default meter tests out of process
antonfirsov Jul 13, 2023
e686bee
oops
antonfirsov Jul 13, 2023
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
9 changes: 9 additions & 0 deletions src/libraries/System.Net.Http/ref/System.Net.Http.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ public HttpClientHandler() { }
public long MaxRequestContentBufferSize { get { throw null; } set { } }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
public int MaxResponseHeadersLength { get { throw null; } set { } }
[System.CLSCompliantAttribute(false)]
public System.Diagnostics.Metrics.Meter Meter { get { throw null; } set { } }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")]
public bool PreAuthenticate { get { throw null; } set { } }
public System.Collections.Generic.IDictionary<string, object?> Properties { get { throw null; } }
Expand Down Expand Up @@ -299,6 +301,11 @@ public sealed class HttpRequestOptions : System.Collections.Generic.IDictionary<
public void Set<TValue>(HttpRequestOptionsKey<TValue> key, TValue value) { throw null; }
}

public static class HttpRequestOptionsExtensions
{
public static void SetCustomMetricsTags(this HttpRequestOptions options, IReadOnlyCollection<KeyValuePair<string, object?>> tags) { throw null; }
antonfirsov marked this conversation as resolved.
Show resolved Hide resolved
}

public partial class HttpResponseMessage : System.IDisposable
{
public HttpResponseMessage() { }
Expand Down Expand Up @@ -391,6 +398,8 @@ public SocketsHttpHandler() { }
public int MaxConnectionsPerServer { get { throw null; } set { } }
public int MaxResponseDrainSize { get { throw null; } set { } }
public int MaxResponseHeadersLength { get { throw null; } set { } }
[System.CLSCompliantAttribute(false)]
public System.Diagnostics.Metrics.Meter Meter { get { throw null; } set { } }
public System.TimeSpan PooledConnectionIdleTimeout { get { throw null; } set { } }
public System.TimeSpan PooledConnectionLifetime { get { throw null; } set { } }
public bool PreAuthenticate { get { throw null; } set { } }
Expand Down
2 changes: 2 additions & 0 deletions src/libraries/System.Net.Http/src/System.Net.Http.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,14 @@
<Compile Include="System\Net\Http\HttpRequestException.cs" />
<Compile Include="System\Net\Http\HttpRequestMessage.cs" />
<Compile Include="System\Net\Http\HttpRequestOptions.cs" />
<Compile Include="System\Net\Http\HttpRequestOptionsExtensions.cs" />
<Compile Include="System\Net\Http\HttpRequestOptionsKey.cs" />
<Compile Include="System\Net\Http\HttpResponseMessage.cs" />
<Compile Include="System\Net\Http\HttpRuleParser.cs" />
<Compile Include="System\Net\Http\HttpTelemetry.cs" />
<Compile Include="System\Net\Http\HttpVersionPolicy.cs" />
<Compile Include="System\Net\Http\MessageProcessingHandler.cs" />
<Compile Include="System\Net\Http\MetricsHandler.cs" />
<Compile Include="System\Net\Http\MultipartContent.cs" />
<Compile Include="System\Net\Http\MultipartFormDataContent.cs" />
<Compile Include="System\Net\Http\NetEventSource.Http.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Threading.Tasks;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics;
using System.Diagnostics.Metrics;

namespace System.Net.Http
{
Expand Down Expand Up @@ -91,6 +92,13 @@ public int MaxResponseDrainSize
set => throw new PlatformNotSupportedException();
}

[CLSCompliant(false)]
public Meter Meter
{
get => throw new PlatformNotSupportedException();
set => throw new PlatformNotSupportedException();
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
}

public TimeSpan ResponseDrainTimeout
{
get => throw new PlatformNotSupportedException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,7 @@ private void HandleFailure(Exception e, bool telemetryStarted, HttpResponseMessa
}

LogRequestFailed(e, telemetryStarted);
response?._requestFailedMetricsLogger?.LogRequestFailed(response);

if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, e);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Metrics;
using System.Globalization;
using System.Net.Security;
using System.Reflection;
Expand Down Expand Up @@ -73,6 +74,13 @@ protected override void Dispose(bool disposing)
base.Dispose(disposing);
}

[CLSCompliant(false)]
public Meter Meter
{
get => throw new NotImplementedException();
set => throw new NotImplementedException();
}

[UnsupportedOSPlatform("browser")]
public bool UseCookies
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Diagnostics.Metrics;
#if TARGET_BROWSER
using HttpHandlerType = System.Net.Http.BrowserHttpHandler;
#else
Expand All @@ -21,11 +22,10 @@ namespace System.Net.Http
public partial class HttpClientHandler : HttpMessageHandler
{
private readonly HttpHandlerType _underlyingHandler;
private HttpMessageHandler? _handler;

#if TARGET_BROWSER
private HttpMessageHandler Handler { get; }
#else
private HttpHandlerType Handler => _underlyingHandler;
private Meter _meter = MetricsHandler.DefaultMeter;
#endif

private ClientCertificateOption _clientCertificateOptions;
Expand All @@ -36,15 +36,25 @@ public HttpClientHandler()
{
_underlyingHandler = new HttpHandlerType();

ClientCertificateOptions = ClientCertificateOption.Manual;
}

private HttpMessageHandler SetupHandlerChain()
{
HttpMessageHandler handler = _underlyingHandler;
#if TARGET_BROWSER
Handler = _underlyingHandler;
if (DiagnosticsHandler.IsGloballyEnabled())
{
Handler = new DiagnosticsHandler(Handler, DistributedContextPropagator.Current);
handler = new DiagnosticsHandler(handler, DistributedContextPropagator.Current);
}
handler = new MetricsHandler(handler, _meter);
#endif

ClientCertificateOptions = ClientCertificateOption.Manual;
// Ensure a single handler is used for all requests.
if (Interlocked.CompareExchange(ref _handler, handler, null) != null)
{
handler.Dispose();
}
return handler;
}

protected override void Dispose(bool disposing)
Expand All @@ -62,6 +72,29 @@ protected override void Dispose(bool disposing)
public virtual bool SupportsProxy => HttpHandlerType.SupportsProxy;
public virtual bool SupportsRedirectConfiguration => HttpHandlerType.SupportsRedirectConfiguration;

[CLSCompliant(false)]
public Meter Meter
{
#if TARGET_BROWSER
get => _meter;
set
{
ArgumentNullException.ThrowIfNull(value);
if (value.Name != "System.Net.Http")
{
throw new ArgumentException("Meter name must be 'System.Net.Http'.");
}

CheckDisposedOrStarted();
_meter = value;
}
#else
get => _underlyingHandler.Meter;
set => _underlyingHandler.Meter = value;
#endif
}


[UnsupportedOSPlatform("browser")]
public bool UseCookies
{
Expand Down Expand Up @@ -304,10 +337,10 @@ public SslProtocols SslProtocols
//[UnsupportedOSPlatform("ios")]
//[UnsupportedOSPlatform("tvos")]
protected internal override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken) =>
Handler.Send(request, cancellationToken);
(_handler ?? SetupHandlerChain()).Send(request, cancellationToken);

protected internal override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) =>
Handler.SendAsync(request, cancellationToken);
(_handler ?? SetupHandlerChain()).SendAsync(request, cancellationToken);

// lazy-load the validator func so it can be trimmed by the ILLinker if it isn't used.
private static Func<HttpRequestMessage, X509Certificate2?, X509Chain?, SslPolicyErrors, bool>? s_dangerousAcceptAnyServerCertificateValidator;
Expand All @@ -323,5 +356,16 @@ private void ThrowForModifiedManagedSslOptionsIfStarted()
// SslOptions is changed, since SslOptions itself does not do any such checks.
_underlyingHandler.SslOptions = _underlyingHandler.SslOptions;
}

#if TARGET_BROWSER
private void CheckDisposedOrStarted()
{
ObjectDisposedException.ThrowIf(_disposed, this);
if (_handler != null)
{
throw new InvalidOperationException(SR.net_http_operation_started);
}
}
#endif
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.IO;
using System.Net.Http.Headers;
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -124,7 +122,6 @@ protected virtual void Dispose(bool disposing)
if (disposing && !_disposed)
{
_disposed = true;

if (_disposeHandler)
{
_handler.Dispose();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class HttpRequestMessage : IDisposable
private Version _version;
private HttpVersionPolicy _versionPolicy;
private HttpContent? _content;
private HttpRequestOptions? _options;
internal HttpRequestOptions? _options;

public Version Version
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

namespace System.Net.Http
{
public static class HttpRequestOptionsExtensions
{
private static readonly HttpRequestOptionsKey<IReadOnlyCollection<KeyValuePair<string, object?>>> s_CustomMetricsTagsKey = new("CustomMetricsTags");

public static void SetCustomMetricsTags(this HttpRequestOptions options, IReadOnlyCollection<KeyValuePair<string, object?>> tags)
{
options.Set(s_CustomMetricsTagsKey, tags);
}

internal static bool TryGetCustomMetricsTags(this HttpRequestOptions options, [MaybeNullWhen(false)] out IReadOnlyCollection<KeyValuePair<string, object?>>? tags) =>
options.TryGetValue(s_CustomMetricsTagsKey, out tags);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public class HttpResponseMessage : IDisposable
private HttpContent? _content;
private bool _disposed;

internal IRequestFailureMetricsLogger? _requestFailedMetricsLogger;

public Version Version
{
get { return _version; }
Expand Down
Loading