Skip to content

Commit dcc66a7

Browse files
authored
Privately expose MsQuic performance counters (#98422)
* Dump select counters after running functional tests. * V1 of counter implementation * Fix compilation * Switch to Metrics API * Code review feedback * Rename to QuicTestCollection * Move to instrument per metric * Remove unwanted code * Rename file * Redisable paralellization
1 parent 24a9069 commit dcc66a7

13 files changed

+363
-37
lines changed

src/libraries/System.Net.Quic/src/System.Net.Quic.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@
131131
<Reference Include="System.Collections.Concurrent" />
132132
<Reference Include="System.Collections.NonGeneric" />
133133
<Reference Include="System.Console" Condition="'$(Configuration)' == 'Debug'" />
134+
<Reference Include="System.Diagnostics.DiagnosticSource" />
134135
<Reference Include="System.Diagnostics.Tracing" />
135136
<Reference Include="System.Memory" />
136137
<Reference Include="System.Net.NameResolution" />

src/libraries/System.Net.Quic/src/System/Net/Quic/Interop/msquic_generated.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,7 @@ internal enum QUIC_PERFORMANCE_COUNTERS
929929
PATH_FAILURE,
930930
SEND_STATELESS_RESET,
931931
SEND_STATELESS_RETRY,
932+
CONN_LOAD_REJECT,
932933
MAX,
933934
}
934935

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Generic;
5+
using System.Diagnostics;
6+
using System.Diagnostics.Tracing;
7+
using System.Diagnostics.Metrics;
8+
using System.Net.Quic;
9+
10+
using Microsoft.Quic;
11+
using static Microsoft.Quic.MsQuic;
12+
13+
namespace System.Net
14+
{
15+
internal sealed partial class NetEventSource
16+
{
17+
private static Meter s_meter = new Meter("Private.InternalDiagnostics.System.Net.Quic.MsQuic");
18+
private static long s_countersLastFetched;
19+
private static readonly long[] s_counters = new long[(int)QUIC_PERFORMANCE_COUNTERS.MAX];
20+
public static readonly ObservableCounter<long> s_CONN_CREATED = s_meter.CreateObservableCounter<long>(
21+
name: "msquic.connection.created",
22+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_CREATED),
23+
unit: "{connection}",
24+
description: "New connections allocated");
25+
26+
public static readonly ObservableCounter<long> s_CONN_HANDSHAKE_FAIL = s_meter.CreateObservableCounter<long>(
27+
name: "msquic.connection.handshake_failures",
28+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_HANDSHAKE_FAIL),
29+
unit: "{connection}",
30+
description: "Connections that failed during handshake");
31+
32+
public static readonly ObservableCounter<long> s_CONN_APP_REJECT = s_meter.CreateObservableCounter<long>(
33+
name: "msquic.connection.app_rejected",
34+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_APP_REJECT),
35+
unit: "{connection}",
36+
description: "Connections rejected by the application");
37+
38+
public static readonly ObservableCounter<long> s_CONN_LOAD_REJECT = s_meter.CreateObservableCounter<long>(
39+
name: "msquic.connection.load_rejected",
40+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_LOAD_REJECT),
41+
unit: "{connection}",
42+
description: "Connections rejected due to worker load.");
43+
44+
public static readonly ObservableCounter<long> s_CONN_RESUMED = s_meter.CreateObservableCounter<long>(
45+
name: "msquic.connection.resumed",
46+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_RESUMED),
47+
unit: "{connection}",
48+
description: "Connections resumed");
49+
50+
public static readonly ObservableGauge<long> s_CONN_ACTIVE = s_meter.CreateObservableGauge<long>(
51+
name: "msquic.connection.allocated",
52+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_ACTIVE),
53+
unit: "{connection}",
54+
description: "Connections currently allocated");
55+
56+
public static readonly ObservableGauge<long> s_CONN_CONNECTED = s_meter.CreateObservableGauge<long>(
57+
name: "msquic.connection.connected",
58+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_CONNECTED),
59+
unit: "{connection}",
60+
description: "Connections currently in the connected state");
61+
62+
public static readonly ObservableCounter<long> s_CONN_PROTOCOL_ERRORS = s_meter.CreateObservableCounter<long>(
63+
name: "msquic.connection.protocol_errors",
64+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_PROTOCOL_ERRORS),
65+
unit: "{connection}",
66+
description: "Connections shutdown with a protocol error");
67+
68+
public static readonly ObservableCounter<long> s_CONN_NO_ALPN = s_meter.CreateObservableCounter<long>(
69+
name: "msquic.connection.no_alpn",
70+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_NO_ALPN),
71+
unit: "{connection}",
72+
description: "Connection attempts with no matching ALPN");
73+
74+
public static readonly ObservableGauge<long> s_STRM_ACTIVE = s_meter.CreateObservableGauge<long>(
75+
name: "msquic.stream.allocated",
76+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.STRM_ACTIVE),
77+
unit: "{stream}",
78+
description: "Current streams allocated");
79+
80+
public static readonly ObservableCounter<long> s_PKTS_SUSPECTED_LOST = s_meter.CreateObservableCounter<long>(
81+
name: "msquic.packet.suspected_lost",
82+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.PKTS_SUSPECTED_LOST),
83+
unit: "{packet}",
84+
description: "Packets suspected lost");
85+
86+
public static readonly ObservableCounter<long> s_PKTS_DROPPED = s_meter.CreateObservableCounter<long>(
87+
name: "msquic.packet.dropped",
88+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.PKTS_DROPPED),
89+
unit: "{packet}",
90+
description: "Packets dropped for any reason");
91+
92+
public static readonly ObservableCounter<long> s_PKTS_DECRYPTION_FAIL = s_meter.CreateObservableCounter<long>(
93+
name: "msquic.packet.decryption_failures",
94+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.PKTS_DECRYPTION_FAIL),
95+
unit: "{packet}",
96+
description: "Packets with decryption failures");
97+
98+
public static readonly ObservableCounter<long> s_UDP_RECV = s_meter.CreateObservableCounter<long>(
99+
name: "msquic.udp.recv_datagrams",
100+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.UDP_RECV),
101+
unit: "{datagram}",
102+
description: "UDP datagrams received");
103+
104+
public static readonly ObservableCounter<long> s_UDP_SEND = s_meter.CreateObservableCounter<long>(
105+
name: "msquic.udp.send_datagrams",
106+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.UDP_SEND),
107+
unit: "{datagram}",
108+
description: "UDP datagrams sent");
109+
110+
public static readonly ObservableCounter<long> s_UDP_RECV_BYTES = s_meter.CreateObservableCounter<long>(
111+
name: "msquic.udp.recv_bytes",
112+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.UDP_RECV_BYTES),
113+
unit: "By",
114+
description: "UDP payload bytes received");
115+
116+
public static readonly ObservableCounter<long> s_UDP_SEND_BYTES = s_meter.CreateObservableCounter<long>(
117+
name: "msquic.udp.send_bytes",
118+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.UDP_SEND_BYTES),
119+
unit: "By",
120+
description: "UDP payload bytes sent");
121+
122+
public static readonly ObservableCounter<long> s_UDP_RECV_EVENTS = s_meter.CreateObservableCounter<long>(
123+
name: "msquic.udp.recv_events",
124+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.UDP_RECV_EVENTS),
125+
unit: "{event}",
126+
description: "UDP receive events");
127+
128+
public static readonly ObservableCounter<long> s_UDP_SEND_CALLS = s_meter.CreateObservableCounter<long>(
129+
name: "msquic.udp.send_calls",
130+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.UDP_SEND_CALLS),
131+
unit: "{call}",
132+
description: "UDP send API calls");
133+
134+
public static readonly ObservableCounter<long> s_APP_SEND_BYTES = s_meter.CreateObservableCounter<long>(
135+
name: "msquic.app.send_bytes",
136+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.APP_SEND_BYTES),
137+
unit: "By",
138+
description: "Bytes sent by applications");
139+
140+
public static readonly ObservableCounter<long> s_APP_RECV_BYTES = s_meter.CreateObservableCounter<long>(
141+
name: "msquic.app.recv_bytes",
142+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.APP_RECV_BYTES),
143+
unit: "By",
144+
description: "Bytes received by applications");
145+
146+
public static readonly ObservableGauge<long> s_CONN_QUEUE_DEPTH = s_meter.CreateObservableGauge<long>(
147+
name: "msquic.threadpool.conn_queue_depth",
148+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_QUEUE_DEPTH),
149+
unit: "{connection}",
150+
description: "Current connections queued for processing");
151+
152+
public static readonly ObservableGauge<long> s_CONN_OPER_QUEUE_DEPTH = s_meter.CreateObservableGauge<long>(
153+
name: "msquic.threadpool.conn_oper_queue_depth",
154+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_OPER_QUEUE_DEPTH),
155+
unit: "{operation}",
156+
description: "Current connection operations queued");
157+
158+
public static readonly ObservableCounter<long> s_CONN_OPER_QUEUED = s_meter.CreateObservableCounter<long>(
159+
name: "msquic.threadpool.conn_oper_queued",
160+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_OPER_QUEUED),
161+
unit: "{operation}",
162+
description: "New connection operations queued");
163+
164+
public static readonly ObservableCounter<long> s_CONN_OPER_COMPLETED = s_meter.CreateObservableCounter<long>(
165+
name: "msquic.threadpool.conn_oper_completed",
166+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_OPER_COMPLETED),
167+
unit: "{operation}",
168+
description: "Connection operations processed");
169+
170+
public static readonly ObservableGauge<long> s_WORK_OPER_QUEUE_DEPTH = s_meter.CreateObservableGauge<long>(
171+
name: "msquic.threadpool.work_oper_queue_depth",
172+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.WORK_OPER_QUEUE_DEPTH),
173+
unit: "{operation}",
174+
description: "Current worker operations queued");
175+
176+
public static readonly ObservableCounter<long> s_WORK_OPER_QUEUED = s_meter.CreateObservableCounter<long>(
177+
name: "msquic.threadpool.work_oper_queued",
178+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.WORK_OPER_QUEUED),
179+
unit: "{operation}",
180+
description: "New worker operations queued");
181+
182+
public static readonly ObservableCounter<long> s_WORK_OPER_COMPLETED = s_meter.CreateObservableCounter<long>(
183+
name: "msquic.threadpool.work_oper_completed",
184+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.WORK_OPER_COMPLETED),
185+
unit: "{operation}",
186+
description: "Worker operations processed");
187+
188+
public static readonly ObservableCounter<long> s_PATH_VALIDATED = s_meter.CreateObservableCounter<long>(
189+
name: "msquic.datapath.path_validated",
190+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.PATH_VALIDATED),
191+
unit: "{challenge}",
192+
description: "Successful path challenges");
193+
194+
public static readonly ObservableCounter<long> s_PATH_FAILURE = s_meter.CreateObservableCounter<long>(
195+
name: "msquic.datapath.path_failure",
196+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.PATH_FAILURE),
197+
unit: "{challenge}",
198+
description: "Unsuccessful path challenges");
199+
200+
public static readonly ObservableCounter<long> s_SEND_STATELESS_RESET = s_meter.CreateObservableCounter<long>(
201+
name: "msquic.datapath.send_stateless_reset",
202+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.SEND_STATELESS_RESET),
203+
unit: "{packet}",
204+
description: "Stateless reset packets sent ever");
205+
206+
public static readonly ObservableCounter<long> s_SEND_STATELESS_RETRY = s_meter.CreateObservableCounter<long>(
207+
name: "msquic.datapath.send_stateless_retry",
208+
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.SEND_STATELESS_RETRY),
209+
unit: "{packet}",
210+
description: "Stateless retry packets sent");
211+
212+
[NonEvent]
213+
private static void UpdateCounters()
214+
{
215+
if (!MsQuicApi.IsQuicSupported)
216+
{
217+
// Avoid calling into MsQuic if not supported (or not initialized yet)
218+
return;
219+
}
220+
221+
unsafe
222+
{
223+
fixed (long* pCounters = s_counters)
224+
{
225+
uint size = (uint)s_counters.Length * sizeof(long);
226+
MsQuicApi.Api.ApiTable->GetParam(null, QUIC_PARAM_GLOBAL_PERF_COUNTERS, &size, (byte*)pCounters);
227+
}
228+
}
229+
}
230+
231+
[NonEvent]
232+
private static long GetCounterValue(QUIC_PERFORMANCE_COUNTERS counter)
233+
{
234+
//
235+
// We wan't to avoid refreshing the counter values array for each counter callback,
236+
// so we refresh the counters array only once every 50ms. This should be enough time
237+
// for all the counters to be queried and at the same time but still low enough to not
238+
// confuse any monitoring tool as their polling rate is usually in seconds.
239+
//
240+
if (s_countersLastFetched == 0 || Stopwatch.GetElapsedTime(s_countersLastFetched).TotalMilliseconds > 50)
241+
{
242+
UpdateCounters();
243+
s_countersLastFetched = Stopwatch.GetTimestamp();
244+
}
245+
246+
return s_counters[(int)counter];
247+
}
248+
}
249+
}

src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicCipherSuitesPolicyTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
namespace System.Net.Quic.Tests
1010
{
11-
[Collection(nameof(DisableParallelization))]
11+
[Collection(nameof(QuicTestCollection))]
1212
[ConditionalClass(typeof(QuicTestBase), nameof(QuicTestBase.IsSupported), nameof(QuicTestBase.IsNotArm32CoreClrStressTest))]
1313
[SkipOnPlatform(TestPlatforms.Windows, "CipherSuitesPolicy is not supported on Windows")]
1414
public class MsQuicCipherSuitesPolicyTests : QuicTestBase
@@ -77,4 +77,4 @@ await Assert.ThrowsAsync<QuicException>(() => TestConnection(
7777
));
7878
}
7979
}
80-
}
80+
}

src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicPlatformDetectionTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
namespace System.Net.Quic.Tests
1010
{
11+
[Collection(nameof(QuicTestCollection))]
1112
public class MsQuicPlatformDetectionTests : QuicTestBase
1213
{
1314
public MsQuicPlatformDetectionTests(ITestOutputHelper output) : base(output) { }

src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicRemoteExecutorTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
namespace System.Net.Quic.Tests
1414
{
15-
[Collection(nameof(DisableParallelization))]
15+
[Collection(nameof(QuicTestCollection))]
1616
[ConditionalClass(typeof(QuicTestBase), nameof(QuicTestBase.IsSupported), nameof(QuicTestBase.IsNotArm32CoreClrStressTest))]
1717
public class MsQuicRemoteExecutorTests : QuicTestBase
1818
{

src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public void Dispose()
4646
}
4747
}
4848

49-
[Collection(nameof(DisableParallelization))]
49+
[Collection(nameof(QuicTestCollection))]
5050
[ConditionalClass(typeof(QuicTestBase), nameof(QuicTestBase.IsSupported), nameof(QuicTestBase.IsNotArm32CoreClrStressTest))]
5151
public class MsQuicTests : QuicTestBase, IClassFixture<CertificateSetup>
5252
{

src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace System.Net.Quic.Tests
1313
{
1414
using Configuration = System.Net.Test.Common.Configuration;
1515

16-
[Collection(nameof(DisableParallelization))]
16+
[Collection(nameof(QuicTestCollection))]
1717
[ConditionalClass(typeof(QuicTestBase), nameof(QuicTestBase.IsSupported), nameof(QuicTestBase.IsNotArm32CoreClrStressTest))]
1818
public sealed class QuicConnectionTests : QuicTestBase
1919
{

src/libraries/System.Net.Quic/tests/FunctionalTests/QuicListenerTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
namespace System.Net.Quic.Tests
1515
{
16-
[Collection(nameof(DisableParallelization))]
16+
[Collection(nameof(QuicTestCollection))]
1717
[ConditionalClass(typeof(QuicTestBase), nameof(QuicTestBase.IsSupported), nameof(QuicTestBase.IsNotArm32CoreClrStressTest))]
1818
public sealed class QuicListenerTests : QuicTestBase
1919
{

src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamConnectedStreamConformanceTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
namespace System.Net.Quic.Tests
1616
{
17-
[Collection(nameof(DisableParallelization))]
17+
[Collection(nameof(QuicTestCollection))]
1818
[ConditionalClass(typeof(QuicTestBase), nameof(QuicTestBase.IsSupported), nameof(QuicTestBase.IsNotArm32CoreClrStressTest))]
1919
public sealed class QuicStreamConformanceTests : ConnectedStreamConformanceTests
2020
{

src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
namespace System.Net.Quic.Tests
1414
{
15-
[Collection(nameof(DisableParallelization))]
15+
[Collection(nameof(QuicTestCollection))]
1616
[ConditionalClass(typeof(QuicTestBase), nameof(QuicTestBase.IsSupported), nameof(QuicTestBase.IsNotArm32CoreClrStressTest))]
1717
public sealed class QuicStreamTests : QuicTestBase
1818
{
@@ -1211,16 +1211,16 @@ async ValueTask ReleaseOnReadsClosedAsync()
12111211

12121212
private const int SmallestPayload = 1;
12131213
private const int SmallPayload = 1024;
1214-
private const int BufferPayload = 64*1024;
1215-
private const int BufferPlusPayload = 64*1024+1;
1216-
private const int BigPayload = 1024*1024*1024;
1214+
private const int BufferPayload = 64 * 1024;
1215+
private const int BufferPlusPayload = 64 * 1024 + 1;
1216+
private const int BigPayload = 1024 * 1024 * 1024;
12171217

12181218
public static IEnumerable<object[]> PayloadSizeAndTwoBools()
12191219
{
1220-
var boolValues = new [] { true, false };
1220+
var boolValues = new[] { true, false };
12211221
var payloadValues = !PlatformDetection.IsInHelix ?
1222-
new [] { SmallestPayload, SmallPayload, BufferPayload, BufferPlusPayload, BigPayload } :
1223-
new [] { SmallestPayload, SmallPayload, BufferPayload, BufferPlusPayload };
1222+
new[] { SmallestPayload, SmallPayload, BufferPayload, BufferPlusPayload, BigPayload } :
1223+
new[] { SmallestPayload, SmallPayload, BufferPayload, BufferPlusPayload };
12241224
return
12251225
from payload in payloadValues
12261226
from bool1 in boolValues

0 commit comments

Comments
 (0)