Skip to content

Add more System.Net.Http counters #38686

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

Closed
wants to merge 3 commits into from

Conversation

MihaZupan
Copy link
Member

Add http11-connections-single-pool-max (HTTP 1.1 connections per pool)
The maximum number (high-water mark) of TCP connections across any HTTP 1.1 connection pool in SocketsHttpHandler.

Add http20-streams-single-connection-max (HTTP 2.0 streams per connection)
The maximum number (high-water mark) of HTTP/2 streams across any HTTP 2.0 connection in SocketsHttpHandler.

Contributes to #37428

@MihaZupan MihaZupan added this to the 5.0.0 milestone Jul 1, 2020
@MihaZupan MihaZupan requested a review from a team July 1, 2020 22:16
@ghost
Copy link

ghost commented Jul 1, 2020

Tagging subscribers to this area: @dotnet/ncl
Notify danmosemsft if you want to be subscribed.

@@ -31,6 +31,10 @@ namespace System.Net.Http
/// <summary>Provides a set of connection pools, each for its own endpoint.</summary>
internal sealed class HttpConnectionPoolManager : IDisposable
{
// Used by HttpTelemetry to calculate statistics over all connection pools
private static readonly ConcurrentDictionary<HttpConnectionPoolManager, object?> s_allConnectionPools =
new ConcurrentDictionary<HttpConnectionPoolManager, object?>();
Copy link
Member Author

Choose a reason for hiding this comment

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

Unfortunately to implement this, we have to keep track of all connection pools, even if telemetry is disabled (since we can't do it after the fact). Is there an alternative approach where we can avoid this?

Copy link
Member Author

Choose a reason for hiding this comment

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

Also, since this is effectively a ConcurrentHashSet, is it more efficient to use object or something like byte for the dummy value?

Copy link
Member

@stephentoub stephentoub Jul 1, 2020

Choose a reason for hiding this comment

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

This is a potentually huge leak. Pools are now rooted until/unless they're explicitly Disposed.

Copy link
Member Author

Choose a reason for hiding this comment

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

Changed to keep WeakReferences around instead, and use a finalizer to ensure the pool manager is removed from the global queue.

}
}
return max;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of maintaining a collection and looping, can you modify HttpConnectionPool to call a HttpTelemetry.SetNewMaximum(x) whenever it opens up a new connection, and Http2Connection to do similar when it opens a new stream? It should be much cheaper this way.

Copy link
Member

Choose a reason for hiding this comment

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

I still question the value of these max counters. Wouldn't a current count be more useful? You could approximate a max on top of it, but you can't do the inverse.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree. A current count seems a lot more useful.

Copy link
Member Author

Choose a reason for hiding this comment

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

can you modify HttpConnectionPool to call a HttpTelemetry.SetNewMaximum(x) whenever it opens up a new connection, and Http2Connection to do similar when it opens a new stream?

The maximum reported here can fluctuate up and down, from what I understand that approach would only allow growing the max over time.

I also don't see the value of this information. @davidsh if you have the time, could you share what use case you had in mind?

By "current count", I assume you mean a sum of all opened http11 connections / all active Http2 streams? (in which case this global list mess isn't needed since we can just increment/decrement a global counter)

Copy link
Member

Choose a reason for hiding this comment

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

By "current count", I assume you mean a sum of all opened http11 connections / all active Http2 streams?

Yup

Copy link
Contributor

Choose a reason for hiding this comment

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

By "current count", I assume you mean a sum of all opened http11 connections / all active Http2 streams

Does it really make sense to count total number of streams on all connections? I think it would be better to count HTTP/2 connections only. HTTP/2 stream is a logical entity in the nutshell whereas HTTP/2 connection is a physical one consuming limited resources (in example SNAT ports).

@MihaZupan
Copy link
Member Author

Closing as we don't see value in these specific counters in favour of counters for current totals

@MihaZupan MihaZupan closed this Jul 2, 2020
Copy link
Contributor

@alnikola alnikola left a comment

Choose a reason for hiding this comment

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

Some comments

@@ -1553,6 +1553,10 @@ private static bool GetIsWindows7Or2008R2()
memberName, // method name
message); // message

public int Http11ConnectionCount => _associatedConnectionCount;

public int Http20StreamCount => _http2Connection?.HttpStreamCount ?? 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we some how make it more friendly to upcoming multiple HTTP/2 connection API?

}
}
return max;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

By "current count", I assume you mean a sum of all opened http11 connections / all active Http2 streams

Does it really make sense to count total number of streams on all connections? I think it would be better to count HTTP/2 connections only. HTTP/2 stream is a logical entity in the nutshell whereas HTTP/2 connection is a physical one consuming limited resources (in example SNAT ports).

{
foreach ((_, HttpConnectionPool connectionPool) in connectionPoolManager._pools)
{
max = Math.Max(max, connectionPool.Http11ConnectionCount);
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd suggest to consider using an associative MaxHeap (i.e. MaxHeap combined with Dictionary to access elements for O(1)) where each node stores open connection count for a single HttpConnectionPool.

On connection open or close, the pool will increase or decrease the respective counter. Since each pool will change only their own counter, it can be easily done thread safely. When the polling counter _maxHttp11ConnectionsPerPoolCounter triggers, it will call Heapify method to rebuild the heap and get the max counter on the top. Heapify runs in O(logn) in the worst case so it seems fast enough.

There is one more issue though, that is to ensure Heapify can run concurrently with heap nodes changes. To solve that, I'd suggest to guard Heapify call with lock, but allow concurrent changes to counters on heap as explained above.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thank you for the idea! While having a better O() when collecting the maximum on polling intervals, I am doubtful it would be a measurable perf improvement at the end, especially considering the added complexity.

The current approach tries to minimize the overhead when telemetry is disabled. The proposed one would incur an overhead on every connection even when telemetry is disabled.

While the O(N) for this collection might seem scary, it only runs once per polling interval, and the N is effectively capped at the port limit, so I doubt it would show up as a significant overhead.

Copy link
Contributor

Choose a reason for hiding this comment

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

The proposed one would incur an overhead on every connection even when telemetry is disabled.

That's not exactly so. If telemetry is disabled increase/decrease counter methods will be no-op and Heapify will never be called since timer will be disabled as well.

However, I do agree it adds more complexity.

@ghost ghost locked as resolved and limited conversation to collaborators Dec 8, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants