diff --git a/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj b/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj
index 40351864618018..7351b0a3a4555d 100644
--- a/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj
+++ b/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj
@@ -131,6 +131,7 @@
+
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Interop/msquic_generated.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Interop/msquic_generated.cs
index b8c0b092df1df7..ad781e6ddd7c7e 100644
--- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Interop/msquic_generated.cs
+++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Interop/msquic_generated.cs
@@ -929,6 +929,7 @@ internal enum QUIC_PERFORMANCE_COUNTERS
PATH_FAILURE,
SEND_STATELESS_RESET,
SEND_STATELESS_RETRY,
+ CONN_LOAD_REJECT,
MAX,
}
diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/NetEventSource.Quic.Counters.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/NetEventSource.Quic.Counters.cs
new file mode 100644
index 00000000000000..93ec7e7532c7f3
--- /dev/null
+++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/NetEventSource.Quic.Counters.cs
@@ -0,0 +1,249 @@
+// 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;
+using System.Diagnostics.Tracing;
+using System.Diagnostics.Metrics;
+using System.Net.Quic;
+
+using Microsoft.Quic;
+using static Microsoft.Quic.MsQuic;
+
+namespace System.Net
+{
+ internal sealed partial class NetEventSource
+ {
+ private static Meter s_meter = new Meter("Private.InternalDiagnostics.System.Net.Quic.MsQuic");
+ private static long s_countersLastFetched;
+ private static readonly long[] s_counters = new long[(int)QUIC_PERFORMANCE_COUNTERS.MAX];
+ public static readonly ObservableCounter s_CONN_CREATED = s_meter.CreateObservableCounter(
+ name: "msquic.connection.created",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_CREATED),
+ unit: "{connection}",
+ description: "New connections allocated");
+
+ public static readonly ObservableCounter s_CONN_HANDSHAKE_FAIL = s_meter.CreateObservableCounter(
+ name: "msquic.connection.handshake_failures",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_HANDSHAKE_FAIL),
+ unit: "{connection}",
+ description: "Connections that failed during handshake");
+
+ public static readonly ObservableCounter s_CONN_APP_REJECT = s_meter.CreateObservableCounter(
+ name: "msquic.connection.app_rejected",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_APP_REJECT),
+ unit: "{connection}",
+ description: "Connections rejected by the application");
+
+ public static readonly ObservableCounter s_CONN_LOAD_REJECT = s_meter.CreateObservableCounter(
+ name: "msquic.connection.load_rejected",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_LOAD_REJECT),
+ unit: "{connection}",
+ description: "Connections rejected due to worker load.");
+
+ public static readonly ObservableCounter s_CONN_RESUMED = s_meter.CreateObservableCounter(
+ name: "msquic.connection.resumed",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_RESUMED),
+ unit: "{connection}",
+ description: "Connections resumed");
+
+ public static readonly ObservableGauge s_CONN_ACTIVE = s_meter.CreateObservableGauge(
+ name: "msquic.connection.allocated",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_ACTIVE),
+ unit: "{connection}",
+ description: "Connections currently allocated");
+
+ public static readonly ObservableGauge s_CONN_CONNECTED = s_meter.CreateObservableGauge(
+ name: "msquic.connection.connected",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_CONNECTED),
+ unit: "{connection}",
+ description: "Connections currently in the connected state");
+
+ public static readonly ObservableCounter s_CONN_PROTOCOL_ERRORS = s_meter.CreateObservableCounter(
+ name: "msquic.connection.protocol_errors",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_PROTOCOL_ERRORS),
+ unit: "{connection}",
+ description: "Connections shutdown with a protocol error");
+
+ public static readonly ObservableCounter s_CONN_NO_ALPN = s_meter.CreateObservableCounter(
+ name: "msquic.connection.no_alpn",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_NO_ALPN),
+ unit: "{connection}",
+ description: "Connection attempts with no matching ALPN");
+
+ public static readonly ObservableGauge s_STRM_ACTIVE = s_meter.CreateObservableGauge(
+ name: "msquic.stream.allocated",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.STRM_ACTIVE),
+ unit: "{stream}",
+ description: "Current streams allocated");
+
+ public static readonly ObservableCounter s_PKTS_SUSPECTED_LOST = s_meter.CreateObservableCounter(
+ name: "msquic.packet.suspected_lost",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.PKTS_SUSPECTED_LOST),
+ unit: "{packet}",
+ description: "Packets suspected lost");
+
+ public static readonly ObservableCounter s_PKTS_DROPPED = s_meter.CreateObservableCounter(
+ name: "msquic.packet.dropped",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.PKTS_DROPPED),
+ unit: "{packet}",
+ description: "Packets dropped for any reason");
+
+ public static readonly ObservableCounter s_PKTS_DECRYPTION_FAIL = s_meter.CreateObservableCounter(
+ name: "msquic.packet.decryption_failures",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.PKTS_DECRYPTION_FAIL),
+ unit: "{packet}",
+ description: "Packets with decryption failures");
+
+ public static readonly ObservableCounter s_UDP_RECV = s_meter.CreateObservableCounter(
+ name: "msquic.udp.recv_datagrams",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.UDP_RECV),
+ unit: "{datagram}",
+ description: "UDP datagrams received");
+
+ public static readonly ObservableCounter s_UDP_SEND = s_meter.CreateObservableCounter(
+ name: "msquic.udp.send_datagrams",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.UDP_SEND),
+ unit: "{datagram}",
+ description: "UDP datagrams sent");
+
+ public static readonly ObservableCounter s_UDP_RECV_BYTES = s_meter.CreateObservableCounter(
+ name: "msquic.udp.recv_bytes",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.UDP_RECV_BYTES),
+ unit: "By",
+ description: "UDP payload bytes received");
+
+ public static readonly ObservableCounter s_UDP_SEND_BYTES = s_meter.CreateObservableCounter(
+ name: "msquic.udp.send_bytes",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.UDP_SEND_BYTES),
+ unit: "By",
+ description: "UDP payload bytes sent");
+
+ public static readonly ObservableCounter s_UDP_RECV_EVENTS = s_meter.CreateObservableCounter(
+ name: "msquic.udp.recv_events",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.UDP_RECV_EVENTS),
+ unit: "{event}",
+ description: "UDP receive events");
+
+ public static readonly ObservableCounter s_UDP_SEND_CALLS = s_meter.CreateObservableCounter(
+ name: "msquic.udp.send_calls",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.UDP_SEND_CALLS),
+ unit: "{call}",
+ description: "UDP send API calls");
+
+ public static readonly ObservableCounter s_APP_SEND_BYTES = s_meter.CreateObservableCounter(
+ name: "msquic.app.send_bytes",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.APP_SEND_BYTES),
+ unit: "By",
+ description: "Bytes sent by applications");
+
+ public static readonly ObservableCounter s_APP_RECV_BYTES = s_meter.CreateObservableCounter(
+ name: "msquic.app.recv_bytes",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.APP_RECV_BYTES),
+ unit: "By",
+ description: "Bytes received by applications");
+
+ public static readonly ObservableGauge s_CONN_QUEUE_DEPTH = s_meter.CreateObservableGauge(
+ name: "msquic.threadpool.conn_queue_depth",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_QUEUE_DEPTH),
+ unit: "{connection}",
+ description: "Current connections queued for processing");
+
+ public static readonly ObservableGauge s_CONN_OPER_QUEUE_DEPTH = s_meter.CreateObservableGauge(
+ name: "msquic.threadpool.conn_oper_queue_depth",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_OPER_QUEUE_DEPTH),
+ unit: "{operation}",
+ description: "Current connection operations queued");
+
+ public static readonly ObservableCounter s_CONN_OPER_QUEUED = s_meter.CreateObservableCounter(
+ name: "msquic.threadpool.conn_oper_queued",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_OPER_QUEUED),
+ unit: "{operation}",
+ description: "New connection operations queued");
+
+ public static readonly ObservableCounter s_CONN_OPER_COMPLETED = s_meter.CreateObservableCounter(
+ name: "msquic.threadpool.conn_oper_completed",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_OPER_COMPLETED),
+ unit: "{operation}",
+ description: "Connection operations processed");
+
+ public static readonly ObservableGauge s_WORK_OPER_QUEUE_DEPTH = s_meter.CreateObservableGauge(
+ name: "msquic.threadpool.work_oper_queue_depth",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.WORK_OPER_QUEUE_DEPTH),
+ unit: "{operation}",
+ description: "Current worker operations queued");
+
+ public static readonly ObservableCounter s_WORK_OPER_QUEUED = s_meter.CreateObservableCounter(
+ name: "msquic.threadpool.work_oper_queued",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.WORK_OPER_QUEUED),
+ unit: "{operation}",
+ description: "New worker operations queued");
+
+ public static readonly ObservableCounter s_WORK_OPER_COMPLETED = s_meter.CreateObservableCounter(
+ name: "msquic.threadpool.work_oper_completed",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.WORK_OPER_COMPLETED),
+ unit: "{operation}",
+ description: "Worker operations processed");
+
+ public static readonly ObservableCounter s_PATH_VALIDATED = s_meter.CreateObservableCounter(
+ name: "msquic.datapath.path_validated",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.PATH_VALIDATED),
+ unit: "{challenge}",
+ description: "Successful path challenges");
+
+ public static readonly ObservableCounter s_PATH_FAILURE = s_meter.CreateObservableCounter(
+ name: "msquic.datapath.path_failure",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.PATH_FAILURE),
+ unit: "{challenge}",
+ description: "Unsuccessful path challenges");
+
+ public static readonly ObservableCounter s_SEND_STATELESS_RESET = s_meter.CreateObservableCounter(
+ name: "msquic.datapath.send_stateless_reset",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.SEND_STATELESS_RESET),
+ unit: "{packet}",
+ description: "Stateless reset packets sent ever");
+
+ public static readonly ObservableCounter s_SEND_STATELESS_RETRY = s_meter.CreateObservableCounter(
+ name: "msquic.datapath.send_stateless_retry",
+ observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.SEND_STATELESS_RETRY),
+ unit: "{packet}",
+ description: "Stateless retry packets sent");
+
+ [NonEvent]
+ private static void UpdateCounters()
+ {
+ if (!MsQuicApi.IsQuicSupported)
+ {
+ // Avoid calling into MsQuic if not supported (or not initialized yet)
+ return;
+ }
+
+ unsafe
+ {
+ fixed (long* pCounters = s_counters)
+ {
+ uint size = (uint)s_counters.Length * sizeof(long);
+ MsQuicApi.Api.ApiTable->GetParam(null, QUIC_PARAM_GLOBAL_PERF_COUNTERS, &size, (byte*)pCounters);
+ }
+ }
+ }
+
+ [NonEvent]
+ private static long GetCounterValue(QUIC_PERFORMANCE_COUNTERS counter)
+ {
+ //
+ // We wan't to avoid refreshing the counter values array for each counter callback,
+ // so we refresh the counters array only once every 50ms. This should be enough time
+ // for all the counters to be queried and at the same time but still low enough to not
+ // confuse any monitoring tool as their polling rate is usually in seconds.
+ //
+ if (s_countersLastFetched == 0 || Stopwatch.GetElapsedTime(s_countersLastFetched).TotalMilliseconds > 50)
+ {
+ UpdateCounters();
+ s_countersLastFetched = Stopwatch.GetTimestamp();
+ }
+
+ return s_counters[(int)counter];
+ }
+ }
+}
diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicCipherSuitesPolicyTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicCipherSuitesPolicyTests.cs
index 459b9bce810dca..2d95e08c743923 100644
--- a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicCipherSuitesPolicyTests.cs
+++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicCipherSuitesPolicyTests.cs
@@ -8,7 +8,7 @@
namespace System.Net.Quic.Tests
{
- [Collection(nameof(DisableParallelization))]
+ [Collection(nameof(QuicTestCollection))]
[ConditionalClass(typeof(QuicTestBase), nameof(QuicTestBase.IsSupported), nameof(QuicTestBase.IsNotArm32CoreClrStressTest))]
[SkipOnPlatform(TestPlatforms.Windows, "CipherSuitesPolicy is not supported on Windows")]
public class MsQuicCipherSuitesPolicyTests : QuicTestBase
@@ -77,4 +77,4 @@ await Assert.ThrowsAsync(() => TestConnection(
));
}
}
-}
\ No newline at end of file
+}
diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicPlatformDetectionTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicPlatformDetectionTests.cs
index 16f267fc719535..891e6c7350218b 100644
--- a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicPlatformDetectionTests.cs
+++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicPlatformDetectionTests.cs
@@ -8,6 +8,7 @@
namespace System.Net.Quic.Tests
{
+ [Collection(nameof(QuicTestCollection))]
public class MsQuicPlatformDetectionTests : QuicTestBase
{
public MsQuicPlatformDetectionTests(ITestOutputHelper output) : base(output) { }
diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicRemoteExecutorTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicRemoteExecutorTests.cs
index 051ead9b3bb2ec..90cec1a5237ae8 100644
--- a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicRemoteExecutorTests.cs
+++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicRemoteExecutorTests.cs
@@ -12,7 +12,7 @@
namespace System.Net.Quic.Tests
{
- [Collection(nameof(DisableParallelization))]
+ [Collection(nameof(QuicTestCollection))]
[ConditionalClass(typeof(QuicTestBase), nameof(QuicTestBase.IsSupported), nameof(QuicTestBase.IsNotArm32CoreClrStressTest))]
public class MsQuicRemoteExecutorTests : QuicTestBase
{
diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs
index 4a11909d30bfef..31177d6c15e046 100644
--- a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs
+++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs
@@ -46,7 +46,7 @@ public void Dispose()
}
}
- [Collection(nameof(DisableParallelization))]
+ [Collection(nameof(QuicTestCollection))]
[ConditionalClass(typeof(QuicTestBase), nameof(QuicTestBase.IsSupported), nameof(QuicTestBase.IsNotArm32CoreClrStressTest))]
public class MsQuicTests : QuicTestBase, IClassFixture
{
diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs
index 98d72124f0048c..f7c70196e6d15e 100644
--- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs
+++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs
@@ -13,7 +13,7 @@ namespace System.Net.Quic.Tests
{
using Configuration = System.Net.Test.Common.Configuration;
- [Collection(nameof(DisableParallelization))]
+ [Collection(nameof(QuicTestCollection))]
[ConditionalClass(typeof(QuicTestBase), nameof(QuicTestBase.IsSupported), nameof(QuicTestBase.IsNotArm32CoreClrStressTest))]
public sealed class QuicConnectionTests : QuicTestBase
{
diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicListenerTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicListenerTests.cs
index d9e27a9e394c4c..0de2863f902f24 100644
--- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicListenerTests.cs
+++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicListenerTests.cs
@@ -13,7 +13,7 @@
namespace System.Net.Quic.Tests
{
- [Collection(nameof(DisableParallelization))]
+ [Collection(nameof(QuicTestCollection))]
[ConditionalClass(typeof(QuicTestBase), nameof(QuicTestBase.IsSupported), nameof(QuicTestBase.IsNotArm32CoreClrStressTest))]
public sealed class QuicListenerTests : QuicTestBase
{
diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs
index e224bf75c55312..2f8b7a66ff6c8d 100644
--- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs
+++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs
@@ -14,7 +14,7 @@
namespace System.Net.Quic.Tests
{
- [Collection(nameof(DisableParallelization))]
+ [Collection(nameof(QuicTestCollection))]
[ConditionalClass(typeof(QuicTestBase), nameof(QuicTestBase.IsSupported), nameof(QuicTestBase.IsNotArm32CoreClrStressTest))]
public sealed class QuicStreamConformanceTests : ConnectedStreamConformanceTests
{
diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs
index 72d0995823edc3..e82bd1ea9e8c94 100644
--- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs
+++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs
@@ -12,7 +12,7 @@
namespace System.Net.Quic.Tests
{
- [Collection(nameof(DisableParallelization))]
+ [Collection(nameof(QuicTestCollection))]
[ConditionalClass(typeof(QuicTestBase), nameof(QuicTestBase.IsSupported), nameof(QuicTestBase.IsNotArm32CoreClrStressTest))]
public sealed class QuicStreamTests : QuicTestBase
{
@@ -1211,16 +1211,16 @@ async ValueTask ReleaseOnReadsClosedAsync()
private const int SmallestPayload = 1;
private const int SmallPayload = 1024;
- private const int BufferPayload = 64*1024;
- private const int BufferPlusPayload = 64*1024+1;
- private const int BigPayload = 1024*1024*1024;
+ private const int BufferPayload = 64 * 1024;
+ private const int BufferPlusPayload = 64 * 1024 + 1;
+ private const int BigPayload = 1024 * 1024 * 1024;
public static IEnumerable