Skip to content

Commit bf74880

Browse files
authored
Add SocketsHttpHandler connection metrics (#88893)
* Add SocketsHttpHandler connection metrics * PR feedback * Improve assert around _originAuthority * Typo
1 parent 4a45bd8 commit bf74880

File tree

14 files changed

+425
-126
lines changed

14 files changed

+425
-126
lines changed

src/libraries/System.Net.Http/src/System.Net.Http.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,8 @@
211211
<Compile Include="System\Net\Http\SocketsHttpHandler\SystemProxyInfo.cs" />
212212
<Compile Include="System\Net\Http\SocketsHttpHandler\SocksHelper.cs" />
213213
<Compile Include="System\Net\Http\SocketsHttpHandler\SocksException.cs" />
214+
<Compile Include="System\Net\Http\SocketsHttpHandler\Metrics\ConnectionMetrics.cs" />
215+
<Compile Include="System\Net\Http\SocketsHttpHandler\Metrics\SocketsHttpHandlerMetrics.cs" />
214216
<Compile Include="$(CommonPath)System\Net\Security\SslClientAuthenticationOptionsExtensions.cs"
215217
Link="Common\System\Net\Security\SslClientAuthenticationOptionsExtensions.cs" />
216218
<Compile Include="$(CommonPath)System\Net\ExceptionCheck.cs"

src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.AnyMobile.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -744,7 +744,7 @@ private MetricsHandler SetupHandlerChain()
744744
{
745745
handler = new DiagnosticsHandler(handler, DistributedContextPropagator.Current);
746746
}
747-
MetricsHandler metricsHandler = new MetricsHandler(handler, _meterFactory);
747+
MetricsHandler metricsHandler = new MetricsHandler(handler, _meterFactory, out _);
748748

749749
// Ensure a single handler is used for all requests.
750750
if (Interlocked.CompareExchange(ref _metricsHandler, metricsHandler, null) != null)

src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ private MetricsHandler Handler
4242
{
4343
handler = new DiagnosticsHandler(handler, DistributedContextPropagator.Current);
4444
}
45-
MetricsHandler metricsHandler = new MetricsHandler(handler, _meterFactory);
45+
MetricsHandler metricsHandler = new MetricsHandler(handler, _meterFactory, out _);
4646

4747
// Ensure a single handler is used for all requests.
4848
if (Interlocked.CompareExchange(ref _metricsHandler, metricsHandler, null) != null)

src/libraries/System.Net.Http/src/System/Net/Http/Metrics/MetricsHandler.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ internal sealed class MetricsHandler : HttpMessageHandlerStage
1515
private readonly Counter<long> _failedRequests;
1616
private readonly Histogram<double> _requestsDuration;
1717

18-
public MetricsHandler(HttpMessageHandler innerHandler, IMeterFactory? meterFactory)
18+
public MetricsHandler(HttpMessageHandler innerHandler, IMeterFactory? meterFactory, out Meter meter)
1919
{
2020
_innerHandler = innerHandler;
2121

22-
Meter meter = meterFactory?.Create("System.Net.Http") ?? SharedMeter.Instance;
22+
meter = meterFactory?.Create("System.Net.Http") ?? SharedMeter.Instance;
2323

2424
// Meter has a cache for the instruments it owns
2525
_currentRequests = meter.CreateUpDownCounter<long>(

src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ internal sealed partial class Http2Connection : HttpConnectionBase
4848
private bool _receivedSettingsAck;
4949
private int _initialServerStreamWindowSize;
5050
private int _pendingWindowUpdate;
51-
private long _idleSinceTickCount;
5251

5352
private uint _maxConcurrentStreams;
5453
private uint _streamsInUse;
@@ -77,10 +76,6 @@ internal sealed partial class Http2Connection : HttpConnectionBase
7776
// When all requests have completed, the connection will be torn down.
7877
private bool _disposed;
7978

80-
private const int TelemetryStatus_Opened = 1;
81-
private const int TelemetryStatus_Closed = 2;
82-
private int _markedByTelemetryStatus;
83-
8479
private const int MaxStreamId = int.MaxValue;
8580

8681
// Temporary workaround for request burst handling on connection start.
@@ -138,6 +133,7 @@ internal enum KeepAliveState
138133
private volatile KeepAliveState _keepAliveState;
139134

140135
public Http2Connection(HttpConnectionPool pool, Stream stream)
136+
: base(pool)
141137
{
142138
_pool = pool;
143139
_stream = stream;
@@ -162,7 +158,6 @@ public Http2Connection(HttpConnectionPool pool, Stream stream)
162158
_streamsInUse = 0;
163159

164160
_pendingWindowUpdate = 0;
165-
_idleSinceTickCount = Environment.TickCount64;
166161

167162
_keepAlivePingDelay = TimeSpanToMs(_pool.Settings._keepAlivePingDelay);
168163
_keepAlivePingTimeout = TimeSpanToMs(_pool.Settings._keepAlivePingTimeout);
@@ -177,12 +172,6 @@ public Http2Connection(HttpConnectionPool pool, Stream stream)
177172
_maxHeaderListSize = maxHeaderListSize;
178173
}
179174

180-
if (HttpTelemetry.Log.IsEnabled())
181-
{
182-
HttpTelemetry.Log.Http20ConnectionEstablished();
183-
_markedByTelemetryStatus = TelemetryStatus_Opened;
184-
}
185-
186175
if (NetEventSource.Log.IsEnabled()) TraceConnection(_stream);
187176

188177
static long TimeSpanToMs(TimeSpan value)
@@ -327,6 +316,11 @@ public bool TryReserveStream()
327316

328317
if (_streamsInUse < _maxConcurrentStreams)
329318
{
319+
if (_streamsInUse == 0)
320+
{
321+
MarkConnectionAsNotIdle();
322+
}
323+
330324
_streamsInUse++;
331325
return true;
332326
}
@@ -356,7 +350,7 @@ public void ReleaseStream()
356350

357351
if (_streamsInUse == 0)
358352
{
359-
_idleSinceTickCount = Environment.TickCount64;
353+
MarkConnectionAsIdle();
360354

361355
if (_disposed)
362356
{
@@ -1836,7 +1830,7 @@ public override long GetIdleTicks(long nowTicks)
18361830
{
18371831
lock (SyncObject)
18381832
{
1839-
return _streamsInUse == 0 ? nowTicks - _idleSinceTickCount : 0;
1833+
return _streamsInUse == 0 ? base.GetIdleTicks(nowTicks) : 0;
18401834
}
18411835
}
18421836

@@ -1894,13 +1888,7 @@ private void FinalTeardown()
18941888
// ProcessIncomingFramesAsync and ProcessOutgoingFramesAsync respectively, and those methods are
18951889
// responsible for returning the buffers.
18961890

1897-
if (HttpTelemetry.Log.IsEnabled())
1898-
{
1899-
if (Interlocked.Exchange(ref _markedByTelemetryStatus, TelemetryStatus_Closed) == TelemetryStatus_Opened)
1900-
{
1901-
HttpTelemetry.Log.Http20ConnectionClosed();
1902-
}
1903-
}
1891+
MarkConnectionAsClosed();
19041892
}
19051893

19061894
public override void Dispose()

src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3Connection.cs

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
using System.Diagnostics;
1212
using System.Globalization;
1313
using System.Net.Http.Headers;
14-
using System.Net.Security;
1514

1615
namespace System.Net.Http
1716
{
@@ -21,7 +20,6 @@ namespace System.Net.Http
2120
internal sealed class Http3Connection : HttpConnectionBase
2221
{
2322
private readonly HttpConnectionPool _pool;
24-
private readonly HttpAuthority? _origin;
2523
private readonly HttpAuthority _authority;
2624
private readonly byte[]? _altUsedEncodedHeader;
2725
private QuicConnection? _connection;
@@ -48,10 +46,6 @@ internal sealed class Http3Connection : HttpConnectionBase
4846
// A connection-level error will abort any future operations.
4947
private Exception? _abortException;
5048

51-
private const int TelemetryStatus_Opened = 1;
52-
private const int TelemetryStatus_Closed = 2;
53-
private int _markedByTelemetryStatus;
54-
5549
public HttpAuthority Authority => _authority;
5650
public HttpConnectionPool Pool => _pool;
5751
public uint MaxHeaderListSize => _maxHeaderListSize;
@@ -71,10 +65,10 @@ private bool ShuttingDown
7165
}
7266
}
7367

74-
public Http3Connection(HttpConnectionPool pool, HttpAuthority? origin, HttpAuthority authority, QuicConnection connection, bool includeAltUsedHeader)
68+
public Http3Connection(HttpConnectionPool pool, HttpAuthority authority, QuicConnection connection, bool includeAltUsedHeader)
69+
: base(pool)
7570
{
7671
_pool = pool;
77-
_origin = origin;
7872
_authority = authority;
7973
_connection = connection;
8074

@@ -93,12 +87,6 @@ public Http3Connection(HttpConnectionPool pool, HttpAuthority? origin, HttpAutho
9387
_maxHeaderListSize = maxHeaderListSize;
9488
}
9589

96-
if (HttpTelemetry.Log.IsEnabled())
97-
{
98-
HttpTelemetry.Log.Http30ConnectionEstablished();
99-
_markedByTelemetryStatus = TelemetryStatus_Opened;
100-
}
101-
10290
// Errors are observed via Abort().
10391
_ = SendSettingsAsync();
10492

@@ -168,13 +156,7 @@ private void CheckForShutdown()
168156

169157
}, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
170158

171-
if (HttpTelemetry.Log.IsEnabled())
172-
{
173-
if (Interlocked.Exchange(ref _markedByTelemetryStatus, TelemetryStatus_Closed) == TelemetryStatus_Opened)
174-
{
175-
HttpTelemetry.Log.Http30ConnectionClosed();
176-
}
177-
}
159+
MarkConnectionAsClosed();
178160
}
179161
}
180162

@@ -201,6 +183,11 @@ public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, lon
201183
requestStream = new Http3RequestStream(request, this, quicStream);
202184
lock (SyncObj)
203185
{
186+
if (_activeRequests.Count == 0)
187+
{
188+
MarkConnectionAsNotIdle();
189+
}
190+
204191
_activeRequests.Add(quicStream, requestStream);
205192
}
206193
}
@@ -352,11 +339,17 @@ public void RemoveStream(QuicStream stream)
352339
{
353340
lock (SyncObj)
354341
{
355-
bool removed = _activeRequests.Remove(stream);
356-
357-
if (removed && ShuttingDown)
342+
if (_activeRequests.Remove(stream))
358343
{
359-
CheckForShutdown();
344+
if (ShuttingDown)
345+
{
346+
CheckForShutdown();
347+
}
348+
349+
if (_activeRequests.Count == 0)
350+
{
351+
MarkConnectionAsIdle();
352+
}
360353
}
361354
}
362355
}

src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ internal sealed partial class HttpConnection : HttpConnectionBase
6161
private ValueTask<int> _readAheadTask;
6262
private ArrayBuffer _readBuffer;
6363

64-
private long _idleSinceTickCount;
6564
private int _keepAliveTimeoutSeconds; // 0 == no timeout
6665
private bool _inUse;
6766
private bool _detachedFromPool;
@@ -70,13 +69,13 @@ internal sealed partial class HttpConnection : HttpConnectionBase
7069
private bool _connectionClose; // Connection: close was seen on last response
7170

7271
private const int Status_Disposed = 1;
73-
private const int Status_NotDisposedAndTrackedByTelemetry = 2;
7472
private int _disposed;
7573

7674
public HttpConnection(
7775
HttpConnectionPool pool,
7876
Stream stream,
7977
TransportContext? transportContext)
78+
: base(pool)
8079
{
8180
Debug.Assert(pool != null);
8281
Debug.Assert(stream != null);
@@ -89,14 +88,6 @@ public HttpConnection(
8988
_writeBuffer = new ArrayBuffer(InitialWriteBufferSize, usePool: false);
9089
_readBuffer = new ArrayBuffer(InitialReadBufferSize, usePool: false);
9190

92-
_idleSinceTickCount = Environment.TickCount64;
93-
94-
if (HttpTelemetry.Log.IsEnabled())
95-
{
96-
HttpTelemetry.Log.Http11ConnectionEstablished();
97-
_disposed = Status_NotDisposedAndTrackedByTelemetry;
98-
}
99-
10091
if (NetEventSource.Log.IsEnabled()) TraceConnection(_stream);
10192
}
10293

@@ -108,16 +99,11 @@ private void Dispose(bool disposing)
10899
{
109100
// Ensure we're only disposed once. Dispose could be called concurrently, for example,
110101
// if the request and the response were running concurrently and both incurred an exception.
111-
int previousValue = Interlocked.Exchange(ref _disposed, Status_Disposed);
112-
if (previousValue != Status_Disposed)
102+
if (Interlocked.Exchange(ref _disposed, Status_Disposed) != Status_Disposed)
113103
{
114104
if (NetEventSource.Log.IsEnabled()) Trace("Connection closing.");
115105

116-
// Only decrement the connection count if we counted this connection
117-
if (HttpTelemetry.Log.IsEnabled() && previousValue == Status_NotDisposedAndTrackedByTelemetry)
118-
{
119-
HttpTelemetry.Log.Http11ConnectionClosed();
120-
}
106+
MarkConnectionAsClosed();
121107

122108
if (!_detachedFromPool)
123109
{
@@ -270,8 +256,6 @@ private bool CheckKeepAliveTimeoutExceeded()
270256
GetIdleTicks(Environment.TickCount64) >= _keepAliveTimeoutSeconds * 1000;
271257
}
272258

273-
public override long GetIdleTicks(long nowTicks) => nowTicks - _idleSinceTickCount;
274-
275259
public TransportContext? TransportContext => _transportContext;
276260

277261
public HttpConnectionKind Kind => _pool.Kind;
@@ -517,6 +501,8 @@ public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, boo
517501
Debug.Assert(_readBuffer.ActiveLength == 0, "Unexpected data in read buffer");
518502
Debug.Assert(_readAheadTaskStatus != ReadAheadTask_Started);
519503

504+
MarkConnectionAsNotIdle();
505+
520506
TaskCompletionSource<bool>? allowExpect100ToContinue = null;
521507
Task? sendRequestContentTask = null;
522508

@@ -2086,8 +2072,6 @@ private void ReturnConnectionToPool()
20862072
{
20872073
Debug.Assert(!_detachedFromPool, "Should not be detached from pool unless _connectionClose is true");
20882074

2089-
_idleSinceTickCount = Environment.TickCount64;
2090-
20912075
// Put connection back in the pool.
20922076
_pool.RecycleHttp11Connection(this);
20932077
}

0 commit comments

Comments
 (0)