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

Privately expose MsQuic performance counters #98422

Merged
merged 10 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
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
1 change: 1 addition & 0 deletions src/libraries/System.Net.Quic/src/System.Net.Quic.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@
<Reference Include="System.Collections.Concurrent" />
<Reference Include="System.Collections.NonGeneric" />
<Reference Include="System.Console" Condition="'$(Configuration)' == 'Debug'" />
<Reference Include="System.Diagnostics.DiagnosticSource" />
<Reference Include="System.Diagnostics.Tracing" />
<Reference Include="System.Memory" />
<Reference Include="System.Net.NameResolution" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,7 @@ internal enum QUIC_PERFORMANCE_COUNTERS
PATH_FAILURE,
SEND_STATELESS_RESET,
SEND_STATELESS_RETRY,
CONN_LOAD_REJECT,
Copy link
Member Author

Choose a reason for hiding this comment

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

This is related to microsoft/msquic#4120, we should wait until that one merges.

We don't need to wait with MsQuic update before merging, the counter will be 0 until a version which supports it is loaded.

MAX,
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<long> s_CONN_CREATED = s_meter.CreateObservableCounter<long>(
name: "msquic.connection.created",
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_CREATED),
unit: "{connection}",
description: "New connections allocated");

public static readonly ObservableCounter<long> s_CONN_HANDSHAKE_FAIL = s_meter.CreateObservableCounter<long>(
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<long> s_CONN_APP_REJECT = s_meter.CreateObservableCounter<long>(
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<long> s_CONN_LOAD_REJECT = s_meter.CreateObservableCounter<long>(
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<long> s_CONN_RESUMED = s_meter.CreateObservableCounter<long>(
name: "msquic.connection.resumed",
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_RESUMED),
unit: "{connection}",
description: "Connections resumed");

public static readonly ObservableGauge<long> s_CONN_ACTIVE = s_meter.CreateObservableGauge<long>(
name: "msquic.connection.allocated",
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_ACTIVE),
unit: "{connection}",
description: "Connections currently allocated");

public static readonly ObservableGauge<long> s_CONN_CONNECTED = s_meter.CreateObservableGauge<long>(
name: "msquic.connection.connected",
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_CONNECTED),
unit: "{connection}",
description: "Connections currently in the connected state");

public static readonly ObservableCounter<long> s_CONN_PROTOCOL_ERRORS = s_meter.CreateObservableCounter<long>(
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<long> s_CONN_NO_ALPN = s_meter.CreateObservableCounter<long>(
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<long> s_STRM_ACTIVE = s_meter.CreateObservableGauge<long>(
name: "msquic.stream.allocated",
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.STRM_ACTIVE),
unit: "{stream}",
description: "Current streams allocated");

public static readonly ObservableCounter<long> s_PKTS_SUSPECTED_LOST = s_meter.CreateObservableCounter<long>(
name: "msquic.packet.suspected_lost",
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.PKTS_SUSPECTED_LOST),
unit: "{packet}",
description: "Packets suspected lost");

public static readonly ObservableCounter<long> s_PKTS_DROPPED = s_meter.CreateObservableCounter<long>(
name: "msquic.packet.dropped",
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.PKTS_DROPPED),
unit: "{packet}",
description: "Packets dropped for any reason");

public static readonly ObservableCounter<long> s_PKTS_DECRYPTION_FAIL = s_meter.CreateObservableCounter<long>(
name: "msquic.packet.decryption_failures",
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.PKTS_DECRYPTION_FAIL),
unit: "{packet}",
description: "Packets with decryption failures");

public static readonly ObservableCounter<long> s_UDP_RECV = s_meter.CreateObservableCounter<long>(
name: "msquic.udp.recv_datagrams",
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.UDP_RECV),
unit: "{datagram}",
description: "UDP datagrams received");

public static readonly ObservableCounter<long> s_UDP_SEND = s_meter.CreateObservableCounter<long>(
name: "msquic.udp.send_datagrams",
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.UDP_SEND),
unit: "{datagram}",
description: "UDP datagrams sent");

public static readonly ObservableCounter<long> s_UDP_RECV_BYTES = s_meter.CreateObservableCounter<long>(
name: "msquic.udp.recv_bytes",
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.UDP_RECV_BYTES),
unit: "By",
description: "UDP payload bytes received");

public static readonly ObservableCounter<long> s_UDP_SEND_BYTES = s_meter.CreateObservableCounter<long>(
name: "msquic.udp.send_bytes",
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.UDP_SEND_BYTES),
unit: "By",
description: "UDP payload bytes sent");

public static readonly ObservableCounter<long> s_UDP_RECV_EVENTS = s_meter.CreateObservableCounter<long>(
name: "msquic.udp.recv_events",
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.UDP_RECV_EVENTS),
unit: "{event}",
description: "UDP receive events");

public static readonly ObservableCounter<long> s_UDP_SEND_CALLS = s_meter.CreateObservableCounter<long>(
name: "msquic.udp.send_calls",
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.UDP_SEND_CALLS),
unit: "{call}",
description: "UDP send API calls");

public static readonly ObservableCounter<long> s_APP_SEND_BYTES = s_meter.CreateObservableCounter<long>(
name: "msquic.app.send_bytes",
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.APP_SEND_BYTES),
unit: "By",
description: "Bytes sent by applications");

public static readonly ObservableCounter<long> s_APP_RECV_BYTES = s_meter.CreateObservableCounter<long>(
name: "msquic.app.recv_bytes",
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.APP_RECV_BYTES),
unit: "By",
description: "Bytes received by applications");

public static readonly ObservableGauge<long> s_CONN_QUEUE_DEPTH = s_meter.CreateObservableGauge<long>(
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<long> s_CONN_OPER_QUEUE_DEPTH = s_meter.CreateObservableGauge<long>(
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<long> s_CONN_OPER_QUEUED = s_meter.CreateObservableCounter<long>(
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<long> s_CONN_OPER_COMPLETED = s_meter.CreateObservableCounter<long>(
name: "msquic.threadpool.conn_oper_completed",
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.CONN_OPER_COMPLETED),
unit: "{operation}",
description: "Connection operations processed");

public static readonly ObservableGauge<long> s_WORK_OPER_QUEUE_DEPTH = s_meter.CreateObservableGauge<long>(
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<long> s_WORK_OPER_QUEUED = s_meter.CreateObservableCounter<long>(
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<long> s_WORK_OPER_COMPLETED = s_meter.CreateObservableCounter<long>(
name: "msquic.threadpool.work_oper_completed",
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.WORK_OPER_COMPLETED),
unit: "{operation}",
description: "Worker operations processed");

public static readonly ObservableCounter<long> s_PATH_VALIDATED = s_meter.CreateObservableCounter<long>(
name: "msquic.datapath.path_validated",
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.PATH_VALIDATED),
unit: "{challenge}",
description: "Successful path challenges");

public static readonly ObservableCounter<long> s_PATH_FAILURE = s_meter.CreateObservableCounter<long>(
name: "msquic.datapath.path_failure",
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.PATH_FAILURE),
unit: "{challenge}",
description: "Unsuccessful path challenges");

public static readonly ObservableCounter<long> s_SEND_STATELESS_RESET = s_meter.CreateObservableCounter<long>(
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<long> s_SEND_STATELESS_RETRY = s_meter.CreateObservableCounter<long>(
name: "msquic.datapath.send_stateless_retry",
observeValue: () => GetCounterValue(QUIC_PERFORMANCE_COUNTERS.SEND_STATELESS_RETRY),
unit: "{packet}",
description: "Stateless retry packets sent");

[NonEvent]
rzikm marked this conversation as resolved.
Show resolved Hide resolved
private static void UpdateCounters()
rzikm marked this conversation as resolved.
Show resolved Hide resolved
{
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];
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -77,4 +77,4 @@ await Assert.ThrowsAsync<QuicException>(() => TestConnection(
));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

namespace System.Net.Quic.Tests
{
[Collection(nameof(QuicTestCollection))]
public class MsQuicPlatformDetectionTests : QuicTestBase
{
public MsQuicPlatformDetectionTests(ITestOutputHelper output) : base(output) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<CertificateSetup>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
Loading
Loading