From 902369610a3c14a0b66c73190a20d43832874297 Mon Sep 17 00:00:00 2001 From: Brennan Date: Thu, 13 Jun 2019 18:40:00 -0700 Subject: [PATCH] Convert SignalR EventSource usage to new pattern (#11130) --- .../test/Internal/HostingEventSourceTests.cs | 24 +--- .../Microsoft.AspNetCore.Hosting.Tests.csproj | 1 + .../EventSource.Testing/TestEventListener.cs | 29 +++++ .../Internal/HttpConnectionsEventSource.cs | 90 +++++++++----- .../HttpConnectionsEventSourceTests.cs | 112 ++++++++++++++++++ ...t.AspNetCore.Http.Connections.Tests.csproj | 1 + 6 files changed, 207 insertions(+), 50 deletions(-) create mode 100644 src/Shared/EventSource.Testing/TestEventListener.cs create mode 100644 src/SignalR/common/Http.Connections/test/Internal/HttpConnectionsEventSourceTests.cs diff --git a/src/Hosting/Hosting/test/Internal/HostingEventSourceTests.cs b/src/Hosting/Hosting/test/Internal/HostingEventSourceTests.cs index b3405a1e21..42ca6dbd6c 100644 --- a/src/Hosting/Hosting/test/Internal/HostingEventSourceTests.cs +++ b/src/Hosting/Hosting/test/Internal/HostingEventSourceTests.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing.xunit; using Xunit; @@ -241,29 +242,6 @@ namespace Microsoft.AspNetCore.Hosting.Internal return new HostingEventSource(Guid.NewGuid().ToString()); } - private class TestEventListener : EventListener - { - private readonly int _eventId; - - public TestEventListener(int eventId) - { - _eventId = eventId; - } - - public EventWrittenEventArgs EventData { get; private set; } - - protected override void OnEventWritten(EventWrittenEventArgs eventData) - { - // The tests here run in parallel and since the single publisher instance (HostingEventingSource) - // notifies all listener instances in these tests, capture the EventData that a test is explicitly - // looking for and not give back other tests' data. - if (eventData.EventId == _eventId) - { - EventData = eventData; - } - } - } - private class CounterListener : EventListener { private readonly Dictionary> _counters = new Dictionary>(); diff --git a/src/Hosting/Hosting/test/Microsoft.AspNetCore.Hosting.Tests.csproj b/src/Hosting/Hosting/test/Microsoft.AspNetCore.Hosting.Tests.csproj index 520134a335..0254330bbf 100644 --- a/src/Hosting/Hosting/test/Microsoft.AspNetCore.Hosting.Tests.csproj +++ b/src/Hosting/Hosting/test/Microsoft.AspNetCore.Hosting.Tests.csproj @@ -6,6 +6,7 @@ + diff --git a/src/Shared/EventSource.Testing/TestEventListener.cs b/src/Shared/EventSource.Testing/TestEventListener.cs new file mode 100644 index 0000000000..6635dc14fb --- /dev/null +++ b/src/Shared/EventSource.Testing/TestEventListener.cs @@ -0,0 +1,29 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Diagnostics.Tracing; + +namespace Microsoft.AspNetCore.Internal +{ + internal class TestEventListener : EventListener + { + private readonly int _eventId; + + public TestEventListener(int eventId) + { + _eventId = eventId; + } + + public EventWrittenEventArgs EventData { get; private set; } + + protected override void OnEventWritten(EventWrittenEventArgs eventData) + { + // The tests here run in parallel, capture the EventData that a test is explicitly + // looking for and not give back other tests' data. + if (eventData.EventId == _eventId) + { + EventData = eventData; + } + } + } +} diff --git a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionsEventSource.cs b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionsEventSource.cs index 96a5ca36b6..07f1db16ca 100644 --- a/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionsEventSource.cs +++ b/src/SignalR/common/Http.Connections/src/Internal/HttpConnectionsEventSource.cs @@ -1,28 +1,36 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Diagnostics.Tracing; +using System.Threading; using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Http.Connections.Internal { - [EventSource(Name = "Microsoft-AspNetCore-Http-Connections")] internal class HttpConnectionsEventSource : EventSource { public static readonly HttpConnectionsEventSource Log = new HttpConnectionsEventSource(); - private readonly EventCounter _connectionsStarted; - private readonly EventCounter _connectionsStopped; - private readonly EventCounter _connectionsTimedOut; - private readonly EventCounter _connectionDuration; + private PollingCounter _connectionsStartedCounter; + private PollingCounter _connectionsStoppedCounter; + private PollingCounter _connectionsTimedOutCounter; + private PollingCounter _currentConnectionsCounter; + private EventCounter _connectionDuration; - private HttpConnectionsEventSource() + private long _connectionsStarted; + private long _connectionsStopped; + private long _connectionsTimedOut; + private long _currentConnections; + + internal HttpConnectionsEventSource() + : base("Microsoft.AspNetCore.Http.Connections") + { + } + + // Used for testing + internal HttpConnectionsEventSource(string eventSourceName) + : base(eventSourceName) { - _connectionsStarted = new EventCounter("ConnectionsStarted", this); - _connectionsStopped = new EventCounter("ConnectionsStopped", this); - _connectionsTimedOut = new EventCounter("ConnectionsTimedOut", this); - _connectionDuration = new EventCounter("ConnectionDuration", this); } // This has to go through NonEvent because only Primitive types are allowed @@ -30,11 +38,13 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal [NonEvent] public void ConnectionStop(string connectionId, ValueStopwatch timer) { + Interlocked.Increment(ref _connectionsStopped); + Interlocked.Decrement(ref _currentConnections); + if (IsEnabled()) { var duration = timer.IsActive ? timer.GetElapsedTime().TotalMilliseconds : 0.0; - _connectionDuration.WriteMetric((float)duration); - _connectionsStopped.WriteMetric(1.0f); + _connectionDuration.WriteMetric(duration); if (IsEnabled(EventLevel.Informational, EventKeywords.None)) { @@ -46,15 +56,13 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal [Event(eventId: 1, Level = EventLevel.Informational, Message = "Started connection '{0}'.")] public ValueStopwatch ConnectionStart(string connectionId) { - if (IsEnabled()) - { - _connectionsStarted.WriteMetric(1.0f); + Interlocked.Increment(ref _connectionsStarted); + Interlocked.Increment(ref _currentConnections); - if (IsEnabled(EventLevel.Informational, EventKeywords.None)) - { - WriteEvent(1, connectionId); - return ValueStopwatch.StartNew(); - } + if (IsEnabled(EventLevel.Informational, EventKeywords.None)) + { + WriteEvent(1, connectionId); + return ValueStopwatch.StartNew(); } return default; } @@ -65,14 +73,42 @@ namespace Microsoft.AspNetCore.Http.Connections.Internal [Event(eventId: 3, Level = EventLevel.Informational, Message = "Connection '{0}' timed out.")] public void ConnectionTimedOut(string connectionId) { - if (IsEnabled()) - { - _connectionsTimedOut.WriteMetric(1.0f); + Interlocked.Increment(ref _connectionsTimedOut); - if (IsEnabled(EventLevel.Informational, EventKeywords.None)) + if (IsEnabled(EventLevel.Informational, EventKeywords.None)) + { + WriteEvent(3, connectionId); + } + } + + protected override void OnEventCommand(EventCommandEventArgs command) + { + if (command.Command == EventCommand.Enable) + { + // This is the convention for initializing counters in the RuntimeEventSource (lazily on the first enable command). + // They aren't disabled afterwards... + + _connectionsStartedCounter ??= new PollingCounter("connections-started", this, () => _connectionsStarted) { - WriteEvent(3, connectionId); - } + DisplayName = "Total Connections Started", + }; + _connectionsStoppedCounter ??= new PollingCounter("connections-stopped", this, () => _connectionsStopped) + { + DisplayName = "Total Connections Stopped", + }; + _connectionsTimedOutCounter ??= new PollingCounter("connections-timed-out", this, () => _connectionsTimedOut) + { + DisplayName = "Total Connections Timed Out", + }; + _currentConnectionsCounter ??= new PollingCounter("current-connections", this, () => _currentConnections) + { + DisplayName = "Current Connections", + }; + + _connectionDuration ??= new EventCounter("connections-duration", this) + { + DisplayName = "Average Connection Duration", + }; } } diff --git a/src/SignalR/common/Http.Connections/test/Internal/HttpConnectionsEventSourceTests.cs b/src/SignalR/common/Http.Connections/test/Internal/HttpConnectionsEventSourceTests.cs new file mode 100644 index 0000000000..5d5574766a --- /dev/null +++ b/src/SignalR/common/Http.Connections/test/Internal/HttpConnectionsEventSourceTests.cs @@ -0,0 +1,112 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.Tracing; +using Microsoft.AspNetCore.Internal; +using Microsoft.Extensions.Internal; +using Xunit; + +namespace Microsoft.AspNetCore.Http.Connections.Internal +{ + public class HttpConnectionsEventSourceTests + { + [Fact] + public void MatchesNameAndGuid() + { + // Arrange & Act + var eventSource = new HttpConnectionsEventSource(); + + // Assert + Assert.Equal("Microsoft.AspNetCore.Http.Connections", eventSource.Name); + Assert.Equal(Guid.Parse("c26fe4b6-8790-5d41-5900-0f2b6b74efaa"), eventSource.Guid); + } + + [Fact] + public void ConnectionStart() + { + // Arrange + var expectedEventId = 1; + var eventListener = new TestEventListener(expectedEventId); + var httpConnectionsEventSource = GetHttpConnectionEventSource(); + eventListener.EnableEvents(httpConnectionsEventSource, EventLevel.Informational); + + // Act + httpConnectionsEventSource.ConnectionStart("1"); + + // Assert + var eventData = eventListener.EventData; + Assert.NotNull(eventData); + Assert.Equal(expectedEventId, eventData.EventId); + Assert.Equal("ConnectionStart", eventData.EventName); + Assert.Equal(EventLevel.Informational, eventData.Level); + Assert.Same(httpConnectionsEventSource, eventData.EventSource); + Assert.Equal("Started connection '{0}'.", eventData.Message); + Assert.Collection(eventData.Payload, + arg => + { + Assert.Equal("1", arg); + }); + } + + [Fact] + public void ConnectionStop() + { + // Arrange + var expectedEventId = 2; + var eventListener = new TestEventListener(expectedEventId); + var httpConnectionsEventSource = GetHttpConnectionEventSource(); + eventListener.EnableEvents(httpConnectionsEventSource, EventLevel.Informational); + + // Act + var stopWatch = ValueStopwatch.StartNew(); + httpConnectionsEventSource.ConnectionStop("1", stopWatch); + + // Assert + var eventData = eventListener.EventData; + Assert.NotNull(eventData); + Assert.Equal(expectedEventId, eventData.EventId); + Assert.Equal("ConnectionStop", eventData.EventName); + Assert.Equal(EventLevel.Informational, eventData.Level); + Assert.Same(httpConnectionsEventSource, eventData.EventSource); + Assert.Equal("Stopped connection '{0}'.", eventData.Message); + Assert.Collection(eventData.Payload, + arg => + { + Assert.Equal("1", arg); + }); + } + + [Fact] + public void ConnectionTimedOut() + { + // Arrange + var expectedEventId = 3; + var eventListener = new TestEventListener(expectedEventId); + var httpConnectionsEventSource = GetHttpConnectionEventSource(); + eventListener.EnableEvents(httpConnectionsEventSource, EventLevel.Informational); + + // Act + httpConnectionsEventSource.ConnectionTimedOut("1"); + + // Assert + var eventData = eventListener.EventData; + Assert.NotNull(eventData); + Assert.Equal(expectedEventId, eventData.EventId); + Assert.Equal("ConnectionTimedOut", eventData.EventName); + Assert.Equal(EventLevel.Informational, eventData.Level); + Assert.Same(httpConnectionsEventSource, eventData.EventSource); + Assert.Equal("Connection '{0}' timed out.", eventData.Message); + Assert.Collection(eventData.Payload, + arg => + { + Assert.Equal("1", arg); + }); + } + + private static HttpConnectionsEventSource GetHttpConnectionEventSource() + { + return new HttpConnectionsEventSource(Guid.NewGuid().ToString()); + } + } +} diff --git a/src/SignalR/common/Http.Connections/test/Microsoft.AspNetCore.Http.Connections.Tests.csproj b/src/SignalR/common/Http.Connections/test/Microsoft.AspNetCore.Http.Connections.Tests.csproj index 5a8aca685f..01f0769245 100644 --- a/src/SignalR/common/Http.Connections/test/Microsoft.AspNetCore.Http.Connections.Tests.csproj +++ b/src/SignalR/common/Http.Connections/test/Microsoft.AspNetCore.Http.Connections.Tests.csproj @@ -7,6 +7,7 @@ +