diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/AggregationManager.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/AggregationManager.cs index 3e2460e0e993a1..b1035b2b2b544b 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/AggregationManager.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/AggregationManager.cs @@ -21,17 +21,17 @@ internal sealed class AggregationManager // these fields are modified after construction and accessed on multiple threads, use lock(this) to ensure the data // is synchronized private readonly List> _instrumentConfigFuncs = new(); - private TimeSpan _collectionPeriod; + public TimeSpan CollectionPeriod { get; private set; } + public int MaxTimeSeries { get; } + public int MaxHistograms { get; } + private Dictionary _instruments = new(); private readonly ConcurrentDictionary _instrumentStates = new(); private readonly CancellationTokenSource _cts = new(); private Thread? _collectThread; private readonly MeterListener _listener; private int _currentTimeSeries; private int _currentHistograms; - - private readonly int _maxTimeSeries; - private readonly int _maxHistograms; private readonly Action _collectMeasurement; private readonly Action _beginCollection; private readonly Action _endCollection; @@ -59,8 +59,8 @@ public AggregationManager( Action histogramLimitReached, Action observableInstrumentCallbackError) { - _maxTimeSeries = maxTimeSeries; - _maxHistograms = maxHistograms; + MaxTimeSeries = maxTimeSeries; + MaxHistograms = maxHistograms; _collectMeasurement = collectMeasurement; _beginCollection = beginCollection; _endCollection = endCollection; @@ -73,24 +73,10 @@ public AggregationManager( _histogramLimitReached = histogramLimitReached; _observableInstrumentCallbackError = observableInstrumentCallbackError; - _listener = new MeterListener() - { - InstrumentPublished = (instrument, listener) => - { - _instrumentPublished(instrument); - InstrumentState? state = GetInstrumentState(instrument); - if (state != null) - { - _beginInstrumentMeasurements(instrument); - listener.EnableMeasurementEvents(instrument, state); - } - }, - MeasurementsCompleted = (instrument, cookie) => - { - _endInstrumentMeasurements(instrument); - RemoveInstrumentState(instrument); - } - }; + _listener = new MeterListener(); + _listener.InstrumentPublished += PublishedInstrument; + _listener.MeasurementsCompleted += CompletedMeasurements; + _listener.SetMeasurementEventCallback((i, m, l, c) => ((InstrumentState)c!).Update((double)m, l)); _listener.SetMeasurementEventCallback((i, m, l, c) => ((InstrumentState)c!).Update((double)m, l)); _listener.SetMeasurementEventCallback((i, m, l, c) => ((InstrumentState)c!).Update((double)m, l)); @@ -124,16 +110,42 @@ public AggregationManager SetCollectionPeriod(TimeSpan collectionPeriod) Debug.Assert(collectionPeriod.TotalSeconds >= MinCollectionTimeSecs); lock (this) { - _collectionPeriod = collectionPeriod; + CollectionPeriod = collectionPeriod; } return this; } + private void CompletedMeasurements(Instrument instrument, object? cookie) + { + _instruments.Remove(instrument); + _endInstrumentMeasurements(instrument); + RemoveInstrumentState(instrument); + } + + private void PublishedInstrument(Instrument instrument, MeterListener _) + { + _instrumentPublished(instrument); + InstrumentState? state = GetInstrumentState(instrument); + if (state != null) + { + _beginInstrumentMeasurements(instrument); + + if (!_instruments.ContainsKey(instrument)) + { + // This has side effects that prompt MeasurementsCompleted + // to be called if this is called multiple times on an + // instrument in a shared MetricsEventSource. + _listener.EnableMeasurementEvents(instrument, state); + _instruments.Add(instrument, true); + } + } + } + public void Start() { // if already started or already stopped we can't be started again Debug.Assert(_collectThread == null && !_cts.IsCancellationRequested); - Debug.Assert(_collectionPeriod.TotalSeconds >= MinCollectionTimeSecs); + Debug.Assert(CollectionPeriod.TotalSeconds >= MinCollectionTimeSecs); // This explicitly uses a Thread and not a Task so that metrics still work // even when an app is experiencing thread-pool starvation. Although we @@ -148,6 +160,20 @@ public void Start() _initialInstrumentEnumerationComplete(); } + public void Update() + { + // Creating (and destroying) a MeterListener to leverage the existing + // mechanisms for enumerating and publishing instruments. + using (MeterListener tempListener = new MeterListener()) + { + tempListener.InstrumentPublished += PublishedInstrument; + tempListener.MeasurementsCompleted += CompletedMeasurements; + tempListener.Start(); + } + + _initialInstrumentEnumerationComplete(); + } + private void CollectWorker(CancellationToken cancelToken) { try @@ -155,7 +181,7 @@ private void CollectWorker(CancellationToken cancelToken) double collectionIntervalSecs = -1; lock (this) { - collectionIntervalSecs = _collectionPeriod.TotalSeconds; + collectionIntervalSecs = CollectionPeriod.TotalSeconds; } Debug.Assert(collectionIntervalSecs >= MinCollectionTimeSecs); @@ -340,12 +366,12 @@ private void RemoveInstrumentState(Instrument instrument) private bool CheckTimeSeriesAllowed() { - if (_currentTimeSeries < _maxTimeSeries) + if (_currentTimeSeries < MaxTimeSeries) { _currentTimeSeries++; return true; } - else if (_currentTimeSeries == _maxTimeSeries) + else if (_currentTimeSeries == MaxTimeSeries) { _currentTimeSeries++; _timeSeriesLimitReached(); @@ -359,12 +385,12 @@ private bool CheckTimeSeriesAllowed() private bool CheckHistogramAllowed() { - if (_currentHistograms < _maxHistograms) + if (_currentHistograms < MaxHistograms) { _currentHistograms++; return true; } - else if (_currentHistograms == _maxHistograms) + else if (_currentHistograms == MaxHistograms) { _currentHistograms++; _histogramLimitReached(); diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/MetricsEventSource.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/MetricsEventSource.cs index 85231812401a3b..90aef6375a319a 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/MetricsEventSource.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/MetricsEventSource.cs @@ -45,6 +45,14 @@ internal sealed class MetricsEventSource : EventSource { public static readonly MetricsEventSource Log = new(); + private const string SharedSessionId = "SHARED"; + private const string ClientIdKey = "ClientId"; + private const string MaxHistogramsKey = "MaxHistograms"; + private const string MaxTimeSeriesKey = "MaxTimeSeries"; + private const string RefreshIntervalKey = "RefreshInterval"; + private const string DefaultValueDescription = "default"; + private const string SharedValueDescription = "shared value"; + public static class Keywords { /// @@ -217,6 +225,16 @@ public void UpDownCounterRateValuePublished(string sessionId, string meterName, WriteEvent(16, sessionId, meterName, meterVersion ?? "", instrumentName, unit ?? "", tags, rate, value); } + [Event(17, Keywords = Keywords.TimeSeriesValues)] +#if !NET8_0_OR_GREATER + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", + Justification = "This calls WriteEvent with all primitive arguments which is safe. Primitives are always serialized properly.")] +#endif + public void MultipleSessionsConfiguredIncorrectlyError(string clientId, string expectedMaxHistograms, string actualMaxHistograms, string expectedMaxTimeSeries, string actualMaxTimeSeries, string expectedRefreshInterval, string actualRefreshInterval) + { + WriteEvent(17, clientId, expectedMaxHistograms, actualMaxHistograms, expectedMaxTimeSeries, actualMaxTimeSeries, expectedRefreshInterval, actualRefreshInterval); + } + /// /// Called when the EventSource gets a command from a EventListener or ETW. /// @@ -237,6 +255,9 @@ private sealed class CommandHandler { private AggregationManager? _aggregationManager; private string _sessionId = ""; + private HashSet _sharedSessionClientIds = new HashSet(); + private int _sharedSessionRefCount; + private bool _disabledRefCount; public CommandHandler(MetricsEventSource parent) { @@ -245,6 +266,12 @@ public CommandHandler(MetricsEventSource parent) public MetricsEventSource Parent { get; private set;} + public bool IsSharedSession(string commandSessionId) + { + // commandSessionId may be null if it's the disable command + return _sessionId.Equals(SharedSessionId) && (string.IsNullOrEmpty(commandSessionId) || commandSessionId.Equals(SharedSessionId)); + } + public void OnEventCommand(EventCommandEventArgs command) { try @@ -262,14 +289,76 @@ public void OnEventCommand(EventCommandEventArgs command) return; } #endif - if (command.Command == EventCommand.Update || command.Command == EventCommand.Disable || - command.Command == EventCommand.Enable) + + string commandSessionId = GetSessionId(command); + + if ((command.Command == EventCommand.Update + || command.Command == EventCommand.Disable + || command.Command == EventCommand.Enable) + && _aggregationManager != null) { - if (_aggregationManager != null) + if (command.Command == EventCommand.Update + || command.Command == EventCommand.Enable) + { + IncrementRefCount(commandSessionId, command); + } + + if (IsSharedSession(commandSessionId)) + { + if (ShouldDisable(command.Command)) + { + Parent.Message($"Previous session with id {_sessionId} is stopped"); + _aggregationManager.Dispose(); + _aggregationManager = null; + _sessionId = string.Empty; + _sharedSessionClientIds.Clear(); + return; + } + + bool validShared = true; + + double refreshInterval; + lock (_aggregationManager) + { + validShared = SetSharedRefreshIntervalSecs(command.Arguments!, _aggregationManager.CollectionPeriod.TotalSeconds, out refreshInterval) ? validShared : false; + } + + validShared = SetSharedMaxHistograms(command.Arguments!, _aggregationManager.MaxHistograms, out int maxHistograms) ? validShared : false; + validShared = SetSharedMaxTimeSeries(command.Arguments!, _aggregationManager.MaxTimeSeries, out int maxTimeSeries) ? validShared : false; + + if (command.Command != EventCommand.Disable) + { + if (validShared) + { + if (ParseMetrics(command.Arguments!, out string? metricsSpecs)) + { + ParseSpecs(metricsSpecs); + _aggregationManager.Update(); + } + + return; + } + else + { + // If the clientId protocol is not followed, we can't tell which session is configured incorrectly + if (command.Arguments!.TryGetValue(ClientIdKey, out string? clientId)) + { + lock (_aggregationManager) + { + // Use ClientId to identify the session that is not configured correctly (since the sessionId is just SHARED) + Parent.MultipleSessionsConfiguredIncorrectlyError(clientId!, _aggregationManager.MaxHistograms.ToString(), maxHistograms.ToString(), _aggregationManager.MaxTimeSeries.ToString(), maxTimeSeries.ToString(), _aggregationManager.CollectionPeriod.TotalSeconds.ToString(), refreshInterval.ToString()); + } + } + + return; + } + } + } + else { if (command.Command == EventCommand.Enable || command.Command == EventCommand.Update) { - // trying to add more sessions is not supported + // trying to add more sessions is not supported for unshared sessions // EventSource doesn't provide an API that allows us to enumerate the listeners' // filter arguments independently or to easily track them ourselves. For example // removing a listener still shows up as EventCommand.Enable as long as at least @@ -279,84 +368,33 @@ public void OnEventCommand(EventCommandEventArgs command) Parent.MultipleSessionsNotSupportedError(_sessionId); return; } - - _aggregationManager.Dispose(); - _aggregationManager = null; - Parent.Message($"Previous session with id {_sessionId} is stopped"); + else if (ShouldDisable(command.Command)) + { + Parent.Message($"Previous session with id {_sessionId} is stopped"); + _aggregationManager.Dispose(); + _aggregationManager = null; + _sessionId = string.Empty; + _sharedSessionClientIds.Clear(); + return; + } } - _sessionId = ""; } if ((command.Command == EventCommand.Update || command.Command == EventCommand.Enable) && command.Arguments != null) { - if (command.Arguments!.TryGetValue("SessionId", out string? id)) - { - _sessionId = id!; - Parent.Message($"SessionId argument received: {_sessionId}"); - } - else - { - _sessionId = System.Guid.NewGuid().ToString(); - Parent.Message($"New session started. SessionId auto-generated: {_sessionId}"); - } + IncrementRefCount(commandSessionId, command); + _sessionId = commandSessionId; double defaultIntervalSecs = 1; Debug.Assert(AggregationManager.MinCollectionTimeSecs <= defaultIntervalSecs); - double refreshIntervalSecs; - if (command.Arguments!.TryGetValue("RefreshInterval", out string? refreshInterval)) - { - Parent.Message($"RefreshInterval argument received: {refreshInterval}"); - if (!double.TryParse(refreshInterval, out refreshIntervalSecs)) - { - Parent.Message($"Failed to parse RefreshInterval. Using default {defaultIntervalSecs}s."); - refreshIntervalSecs = defaultIntervalSecs; - } - else if (refreshIntervalSecs < AggregationManager.MinCollectionTimeSecs) - { - Parent.Message($"RefreshInterval too small. Using minimum interval {AggregationManager.MinCollectionTimeSecs} seconds."); - refreshIntervalSecs = AggregationManager.MinCollectionTimeSecs; - } - } - else - { - Parent.Message($"No RefreshInterval argument received. Using default {defaultIntervalSecs}s."); - refreshIntervalSecs = defaultIntervalSecs; - } + SetRefreshIntervalSecs(command.Arguments!, AggregationManager.MinCollectionTimeSecs, defaultIntervalSecs, out double refreshIntervalSecs); - int defaultMaxTimeSeries = 1000; - int maxTimeSeries; - if (command.Arguments!.TryGetValue("MaxTimeSeries", out string? maxTimeSeriesString)) - { - Parent.Message($"MaxTimeSeries argument received: {maxTimeSeriesString}"); - if (!int.TryParse(maxTimeSeriesString, out maxTimeSeries)) - { - Parent.Message($"Failed to parse MaxTimeSeries. Using default {defaultMaxTimeSeries}"); - maxTimeSeries = defaultMaxTimeSeries; - } - } - else - { - Parent.Message($"No MaxTimeSeries argument received. Using default {defaultMaxTimeSeries}"); - maxTimeSeries = defaultMaxTimeSeries; - } + const int defaultMaxTimeSeries = 1000; + SetUniqueMaxTimeSeries(command.Arguments!, defaultMaxTimeSeries, out int maxTimeSeries); - int defaultMaxHistograms = 20; - int maxHistograms; - if (command.Arguments!.TryGetValue("MaxHistograms", out string? maxHistogramsString)) - { - Parent.Message($"MaxHistograms argument received: {maxHistogramsString}"); - if (!int.TryParse(maxHistogramsString, out maxHistograms)) - { - Parent.Message($"Failed to parse MaxHistograms. Using default {defaultMaxHistograms}"); - maxHistograms = defaultMaxHistograms; - } - } - else - { - Parent.Message($"No MaxHistogram argument received. Using default {defaultMaxHistograms}"); - maxHistograms = defaultMaxHistograms; - } + const int defaultMaxHistograms = 20; + SetUniqueMaxHistograms(command.Arguments!, defaultMaxHistograms, out int maxHistograms); string sessionId = _sessionId; _aggregationManager = new AggregationManager( @@ -376,15 +414,10 @@ public void OnEventCommand(EventCommandEventArgs command) _aggregationManager.SetCollectionPeriod(TimeSpan.FromSeconds(refreshIntervalSecs)); - if (command.Arguments!.TryGetValue("Metrics", out string? metricsSpecs)) + if (ParseMetrics(command.Arguments!, out string? metricsSpecs)) { - Parent.Message($"Metrics argument received: {metricsSpecs}"); ParseSpecs(metricsSpecs); } - else - { - Parent.Message("No Metrics argument received"); - } _aggregationManager.Start(); } @@ -395,6 +428,165 @@ public void OnEventCommand(EventCommandEventArgs command) } } + private bool ShouldDisable(EventCommand command) + { + return command == EventCommand.Disable + && ((!_disabledRefCount && Interlocked.Decrement(ref _sharedSessionRefCount) == 0) + || !Parent.IsEnabled()); + } + + private bool ParseMetrics(IDictionary arguments, out string? metricsSpecs) + { + if (arguments.TryGetValue("Metrics", out metricsSpecs)) + { + Parent.Message($"Metrics argument received: {metricsSpecs}"); + return true; + } + + Parent.Message("No Metrics argument received"); + return false; + } + + private void InvalidateRefCounting() + { + _disabledRefCount = true; + Parent.Message($"{ClientIdKey} not provided; session will remain active indefinitely."); + } + + private void IncrementRefCount(string clientId, EventCommandEventArgs command) + { + // When creating a SHARED session (i.e. sessionId == SharedSessionId), a randomly-generated clientId + // should be provided as part of the command arguments. If not, we can't tell which session is + // configured incorrectly, and ref-counting will be disabled since there is no way to keep track of + // multiple Enables coming from the same client. This will cause the session to remain active indefinitely. + if (clientId.Equals(SharedSessionId)) + { + if (command.Arguments!.TryGetValue(ClientIdKey, out string? clientIdArg) && !string.IsNullOrEmpty(clientIdArg)) + { + clientId = clientIdArg!; + } + else + { + // If ClientId contract is followed, this should never happen. + InvalidateRefCounting(); + } + } + + if (!_sharedSessionClientIds.Contains(clientId)) + { + _sharedSessionClientIds.Add(clientId); + Interlocked.Increment(ref _sharedSessionRefCount); + } + } + + private bool SetSharedMaxTimeSeries(IDictionary arguments, int sharedValue, out int maxTimeSeries) + { + return SetMaxValue(arguments, MaxTimeSeriesKey, SharedValueDescription, sharedValue, out maxTimeSeries); + } + + private void SetUniqueMaxTimeSeries(IDictionary arguments, int defaultValue, out int maxTimeSeries) + { + _ = SetMaxValue(arguments, MaxTimeSeriesKey, DefaultValueDescription, defaultValue, out maxTimeSeries); + } + + private bool SetSharedMaxHistograms(IDictionary arguments, int sharedValue, out int maxHistograms) + { + return SetMaxValue(arguments, MaxHistogramsKey, SharedValueDescription, sharedValue, out maxHistograms); + } + + private void SetUniqueMaxHistograms(IDictionary arguments, int defaultValue, out int maxHistograms) + { + _ = SetMaxValue(arguments, MaxHistogramsKey, DefaultValueDescription, defaultValue, out maxHistograms); + } + + private bool SetMaxValue(IDictionary arguments, string argumentsKey, string valueDescriptor, int defaultValue, out int maxValue) + { + if (arguments.TryGetValue(argumentsKey, out string? maxString)) + { + Parent.Message($"{argumentsKey} argument received: {maxString}"); + if (!int.TryParse(maxString, out maxValue)) + { + Parent.Message($"Failed to parse {argumentsKey}. Using {valueDescriptor} {defaultValue}"); + maxValue = defaultValue; + } + else if (maxValue != defaultValue) + { + // This is only relevant for shared sessions, where the "default" (provided) value is what is being + // used by the existing session. + return false; + } + } + else + { + Parent.Message($"No {argumentsKey} argument received. Using {valueDescriptor} {defaultValue}"); + maxValue = defaultValue; + } + + return true; + } + + private void SetRefreshIntervalSecs(IDictionary arguments, double minValue, double defaultValue, out double refreshIntervalSeconds) + { + if (GetRefreshIntervalSecs(arguments, DefaultValueDescription, defaultValue, out refreshIntervalSeconds) + && refreshIntervalSeconds < minValue) + { + Parent.Message($"{RefreshIntervalKey} too small. Using minimum interval {minValue} seconds."); + refreshIntervalSeconds = minValue; + } + } + + private bool SetSharedRefreshIntervalSecs(IDictionary arguments, double sharedValue, out double refreshIntervalSeconds) + { + if (GetRefreshIntervalSecs(arguments, SharedValueDescription, sharedValue, out refreshIntervalSeconds) + && refreshIntervalSeconds != sharedValue) + { + return false; + } + + return true; + } + + private bool GetRefreshIntervalSecs(IDictionary arguments, string valueDescriptor, double defaultValue, out double refreshIntervalSeconds) + { + if (arguments!.TryGetValue(RefreshIntervalKey, out string? refreshInterval)) + { + Parent.Message($"{RefreshIntervalKey} argument received: {refreshInterval}"); + if (!double.TryParse(refreshInterval, out refreshIntervalSeconds)) + { + Parent.Message($"Failed to parse {RefreshIntervalKey}. Using {valueDescriptor} {defaultValue}s."); + refreshIntervalSeconds = defaultValue; + return false; + } + } + else + { + Parent.Message($"No {RefreshIntervalKey} argument received. Using {valueDescriptor} {defaultValue}s."); + refreshIntervalSeconds = defaultValue; + return false; + } + + return true; + } + + private string GetSessionId(EventCommandEventArgs command) + { + if (command.Arguments!.TryGetValue("SessionId", out string? id)) + { + Parent.Message($"SessionId argument received: {id!}"); + return id!; + } + + string sessionId = string.Empty; + + if (command.Command != EventCommand.Disable) + { + sessionId = Guid.NewGuid().ToString(); + Parent.Message($"New session started. SessionId auto-generated: {sessionId}"); + } + + return sessionId; + } + private bool LogError(Exception e) { Parent.Error(_sessionId, e.ToString()); diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricEventSourceTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricEventSourceTests.cs index 766d33c853f6cf..5dcd99d7a34282 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricEventSourceTests.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricEventSourceTests.cs @@ -24,6 +24,637 @@ public MetricEventSourceTests(ITestOutputHelper output) _output = output; } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] + public void MultipleListeners_DifferentCounters() + { + using Meter meter = new Meter("TestMeter1"); + Counter c = meter.CreateCounter("counter1"); + + using Meter meter2 = new Meter("TestMeter2"); + Counter c2 = meter2.CreateCounter("counter2"); + + EventWrittenEventArgs[] events, events2; + using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, isShared: true, IntervalSecs, "TestMeter1")) + { + listener.WaitForCollectionStop(s_waitForEventTimeout, 1); + c.Add(5); + listener.WaitForCollectionStop(s_waitForEventTimeout, 2); + c.Add(12); + listener.WaitForCollectionStop(s_waitForEventTimeout, 3); + events = listener.Events.ToArray(); + + using (MetricsEventListener listener2 = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, isShared: true, IntervalSecs, "TestMeter2")) + { + listener2.WaitForCollectionStop(s_waitForEventTimeout, 1); + c2.Add(5); + listener2.WaitForCollectionStop(s_waitForEventTimeout, 2); + c2.Add(12); + listener2.WaitForCollectionStop(s_waitForEventTimeout, 3); + events2 = listener2.Events.ToArray(); + } + } + + AssertBeginInstrumentReportingEventsPresent(events, c); + AssertInitialEnumerationCompleteEventPresent(events); + AssertCounterEventsPresent(events, meter.Name, c.Name, "", "", ("5", "5"), ("12", "17")); + AssertCollectStartStopEventsPresent(events, IntervalSecs, 3); + + AssertBeginInstrumentReportingEventsPresent(events2, c, c2); + AssertInitialEnumerationCompleteEventPresent(events2); + AssertCounterEventsPresent(events2, meter2.Name, c2.Name, "", "", ("5", "5"), ("12", "17")); + AssertCollectStartStopEventsPresent(events2, IntervalSecs, 3); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] + public void MultipleListeners_ReuseCounter() + { + using Meter meter = new Meter("TestMeter1"); + Counter c = meter.CreateCounter("counter1"); + + using Meter meter2 = new Meter("TestMeter2"); + Counter c2 = meter2.CreateCounter("counter2"); + + EventWrittenEventArgs[] events, events2; + using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, isShared: true, IntervalSecs, "TestMeter1")) + { + listener.WaitForCollectionStop(s_waitForEventTimeout, 1); + c.Add(5); + listener.WaitForCollectionStop(s_waitForEventTimeout, 2); + c.Add(12); + listener.WaitForCollectionStop(s_waitForEventTimeout, 3); + events = listener.Events.ToArray(); + + using (MetricsEventListener listener2 = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, isShared: true, IntervalSecs, "TestMeter1", "TestMeter2")) + { + listener2.WaitForCollectionStop(s_waitForEventTimeout, 1); + c.Add(6); + c2.Add(5); + listener2.WaitForCollectionStop(s_waitForEventTimeout, 2); + c.Add(13); + c2.Add(12); + listener2.WaitForCollectionStop(s_waitForEventTimeout, 3); + events2 = listener2.Events.ToArray(); + } + } + + AssertBeginInstrumentReportingEventsPresent(events, c); + AssertInitialEnumerationCompleteEventPresent(events); + AssertCounterEventsPresent(events, meter.Name, c.Name, "", "", ("5", "5"), ("12", "17")); + AssertCollectStartStopEventsPresent(events, IntervalSecs, 3); + + AssertBeginInstrumentReportingEventsPresent(events2, c, c2); + AssertInitialEnumerationCompleteEventPresent(events2); + AssertCounterEventsPresent(events2, meter.Name, c.Name, "", "", ("0", "17"), ("6", "23"), ("13", "36")); + AssertCounterEventsPresent(events2, meter2.Name, c2.Name, "", "", ("5", "5"), ("12", "17")); + AssertCollectStartStopEventsPresent(events2, IntervalSecs, 3); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] + public void MultipleListeners_CollectAfterDisableListener() + { + using Meter meter = new Meter("TestMeter1"); + Counter c = meter.CreateCounter("counter1"); + + using Meter meter2 = new Meter("TestMeter2"); + Counter c2 = meter2.CreateCounter("counter2"); + + EventWrittenEventArgs[] events, events2; + using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, isShared: true, IntervalSecs, "TestMeter1")) + { + listener.WaitForCollectionStop(s_waitForEventTimeout, 1); + c.Add(5); + listener.WaitForCollectionStop(s_waitForEventTimeout, 2); + c.Add(12); + listener.WaitForCollectionStop(s_waitForEventTimeout, 3); + + using (MetricsEventListener listener2 = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, isShared: true, IntervalSecs, "TestMeter2")) + { + listener2.WaitForCollectionStop(s_waitForEventTimeout, 1); + c2.Add(5); + listener2.WaitForCollectionStop(s_waitForEventTimeout, 2); + c2.Add(12); + listener2.WaitForCollectionStop(s_waitForEventTimeout, 3); + events2 = listener2.Events.ToArray(); + } + + listener.WaitForCollectionStop(s_waitForEventTimeout, 7); + c.Add(6); + listener.WaitForCollectionStop(s_waitForEventTimeout, 8); + c.Add(13); + listener.WaitForCollectionStop(s_waitForEventTimeout, 9); + events = listener.Events.ToArray(); + } + + AssertBeginInstrumentReportingEventsPresent(events, c, c, c2); + AssertInitialEnumerationCompleteEventPresent(events, 2); + AssertCounterEventsPresent(events, meter.Name, c.Name, "", "", ("5", "5"), ("12", "17"), ("0", "17"), ("0", "17"), ("0", "17"), ("0", "17"), ("6", "23"), ("13", "36")); + AssertCollectStartStopEventsPresent(events, IntervalSecs, 9); + + AssertBeginInstrumentReportingEventsPresent(events2, c, c2); + AssertInitialEnumerationCompleteEventPresent(events2); + AssertCounterEventsPresent(events2, meter2.Name, c2.Name, "", "", ("5", "5"), ("12", "17")); + AssertCollectStartStopEventsPresent(events2, IntervalSecs, 3); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] + public void MultipleListeners_ThreeCounters() + { + using Meter meter = new Meter("TestMeter1"); + Counter c = meter.CreateCounter("counter1"); + + using Meter meter2 = new Meter("TestMeter2"); + Counter c2 = meter2.CreateCounter("counter2"); + + using Meter meter3 = new Meter("TestMeter3"); + Counter c3 = meter3.CreateCounter("counter3"); + + EventWrittenEventArgs[] events, events2, events3; + using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, isShared: true, IntervalSecs, "TestMeter1")) + { + listener.WaitForCollectionStop(s_waitForEventTimeout, 1); + c.Add(5); + listener.WaitForCollectionStop(s_waitForEventTimeout, 2); + events = listener.Events.ToArray(); + + using (MetricsEventListener listener2 = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, isShared: true, IntervalSecs, "TestMeter2")) + { + listener2.WaitForCollectionStop(s_waitForEventTimeout, 1); + c2.Add(6); + listener2.WaitForCollectionStop(s_waitForEventTimeout, 2); + events2 = listener2.Events.ToArray(); + + using (MetricsEventListener listener3 = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, isShared: true, IntervalSecs, "TestMeter3")) + { + listener3.WaitForCollectionStop(s_waitForEventTimeout, 1); + c3.Add(7); + listener3.WaitForCollectionStop(s_waitForEventTimeout, 2); + events3 = listener3.Events.ToArray(); + } + } + } + + AssertBeginInstrumentReportingEventsPresent(events, c); + AssertInitialEnumerationCompleteEventPresent(events); + AssertCounterEventsPresent(events, meter.Name, c.Name, "", "", ("5", "5")); + AssertCollectStartStopEventsPresent(events, IntervalSecs, 2); + + AssertBeginInstrumentReportingEventsPresent(events2, c, c2); + AssertInitialEnumerationCompleteEventPresent(events2); + AssertCounterEventsPresent(events2, meter2.Name, c2.Name, "", "", ("6", "6")); + AssertCollectStartStopEventsPresent(events2, IntervalSecs, 2); + + AssertBeginInstrumentReportingEventsPresent(events3, c, c2, c3); + AssertInitialEnumerationCompleteEventPresent(events3); + AssertCounterEventsPresent(events3, meter3.Name, c3.Name, "", "", ("7", "7")); + AssertCollectStartStopEventsPresent(events3, IntervalSecs, 2); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] + public void MultipleListeners_OverlappingListeners() + { + using Meter meter = new Meter("TestMeter1"); + Counter c = meter.CreateCounter("counter1"); + + using Meter meter2 = new Meter("TestMeter2"); + Counter c2 = meter2.CreateCounter("counter2"); + + EventWrittenEventArgs[] events, events2; + using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, isShared: true, IntervalSecs, "TestMeter1")) + { + using (MetricsEventListener listener2 = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, isShared: true, IntervalSecs, "TestMeter2")) + { + listener.WaitForCollectionStop(s_waitForEventTimeout, 1); + c.Add(5); + c2.Add(6); + listener.WaitForCollectionStop(s_waitForEventTimeout, 2); + c.Add(12); + c2.Add(13); + listener.WaitForCollectionStop(s_waitForEventTimeout, 3); + events = listener.Events.ToArray(); + events2 = listener2.Events.ToArray(); + } + } + + AssertBeginInstrumentReportingEventsPresent(events, c, c, c2); + AssertBeginInstrumentReportingEventsPresent(events2, c, c2); + AssertInitialEnumerationCompleteEventPresent(events, 2); + AssertInitialEnumerationCompleteEventPresent(events2); + AssertCounterEventsPresent(events, meter.Name, c.Name, "", "", ("5", "5"), ("12", "17")); + AssertCounterEventsPresent(events, meter2.Name, c2.Name, "", "", ("6", "6"), ("13", "19")); + AssertCounterEventsPresent(events2, meter.Name, c.Name, "", "", ("5", "5"), ("12", "17")); + AssertCounterEventsPresent(events2, meter2.Name, c2.Name, "", "", ("6", "6"), ("13", "19")); + AssertCollectStartStopEventsPresent(events, IntervalSecs, 3); + AssertCollectStartStopEventsPresent(events2, IntervalSecs, 3); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] + public void MultipleListeners_UnsharedSessionRejectsUnsharedListener() + { + using Meter meter = new Meter("TestMeter7"); + Counter c = meter.CreateCounter("counter1", "hat", "Fooz!!"); + int counterState = 3; + ObservableCounter oc = meter.CreateObservableCounter("observableCounter1", () => { counterState += 7; return counterState; }, "MB", "Size of universe"); + int gaugeState = 0; + ObservableGauge og = meter.CreateObservableGauge("observableGauge1", () => { gaugeState += 9; return gaugeState; }, "12394923 asd [],;/", "junk!"); + Histogram h = meter.CreateHistogram("histogram1", "a unit", "the description"); + UpDownCounter udc = meter.CreateUpDownCounter("upDownCounter1", "udc unit", "udc description"); + int upDownCounterState = 0; + ObservableUpDownCounter oudc = meter.CreateObservableUpDownCounter("observableUpDownCounter1", () => { upDownCounterState += 11; return upDownCounterState; }, "oudc unit", "oudc description"); + + EventWrittenEventArgs[] events; + using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, IntervalSecs, "TestMeter7")) + { + listener.WaitForCollectionStop(s_waitForEventTimeout, 1); + c.Add(5); + h.Record(19); + udc.Add(33); + listener.WaitForCollectionStop(s_waitForEventTimeout, 2); + c.Add(12); + h.Record(26); + udc.Add(40); + + using MetricsEventListener listener2 = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, IntervalSecs, "TestMeter7"); + listener2.WaitForMultipleSessionsNotSupportedError(s_waitForEventTimeout); + + + listener.WaitForCollectionStop(s_waitForEventTimeout, 3); + events = listener.Events.ToArray(); + } + + AssertBeginInstrumentReportingEventsPresent(events, c, oc, og, h, udc, oudc); + AssertCounterEventsPresent(events, meter.Name, c.Name, "", c.Unit, ("5", "5"), ("12", "17")); + AssertCounterEventsPresent(events, meter.Name, oc.Name, "", oc.Unit, ("", "10"), ("7", "17"), ("7", "24")); + AssertGaugeEventsPresent(events, meter.Name, og.Name, "", og.Unit, "9", "18", "27"); + AssertHistogramEventsPresent(events, meter.Name, h.Name, "", h.Unit, ("0.5=19;0.95=19;0.99=19", "1", "19"), ("0.5=26;0.95=26;0.99=26", "1", "26")); + AssertUpDownCounterEventsPresent(events, meter.Name, udc.Name, "", udc.Unit, ("33", "33"), ("40", "73")); + AssertUpDownCounterEventsPresent(events, meter.Name, oudc.Name, "", oudc.Unit, ("", "11"), ("11", "22"), ("11", "33")); + AssertCollectStartStopEventsPresent(events, IntervalSecs, 3); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] + public void MultipleListeners_UnsharedSessionRejectsSharedListener() + { + using Meter meter = new Meter("TestMeter7"); + Counter c = meter.CreateCounter("counter1", "hat", "Fooz!!"); + int counterState = 3; + ObservableCounter oc = meter.CreateObservableCounter("observableCounter1", () => { counterState += 7; return counterState; }, "MB", "Size of universe"); + int gaugeState = 0; + ObservableGauge og = meter.CreateObservableGauge("observableGauge1", () => { gaugeState += 9; return gaugeState; }, "12394923 asd [],;/", "junk!"); + Histogram h = meter.CreateHistogram("histogram1", "a unit", "the description"); + UpDownCounter udc = meter.CreateUpDownCounter("upDownCounter1", "udc unit", "udc description"); + int upDownCounterState = 0; + ObservableUpDownCounter oudc = meter.CreateObservableUpDownCounter("observableUpDownCounter1", () => { upDownCounterState += 11; return upDownCounterState; }, "oudc unit", "oudc description"); + + EventWrittenEventArgs[] events; + using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, IntervalSecs, "TestMeter7")) + { + listener.WaitForCollectionStop(s_waitForEventTimeout, 1); + c.Add(5); + h.Record(19); + udc.Add(33); + listener.WaitForCollectionStop(s_waitForEventTimeout, 2); + c.Add(12); + h.Record(26); + udc.Add(40); + + using (MetricsEventListener listener2 = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, isShared: true, IntervalSecs, "TestMeter7")) + { + listener2.WaitForMultipleSessionsNotSupportedError(s_waitForEventTimeout); + } + + listener.WaitForCollectionStop(s_waitForEventTimeout, 3); + events = listener.Events.ToArray(); + } + + AssertBeginInstrumentReportingEventsPresent(events, c, oc, og, h, udc, oudc); + AssertCounterEventsPresent(events, meter.Name, c.Name, "", c.Unit, ("5", "5"), ("12", "17")); + AssertCounterEventsPresent(events, meter.Name, oc.Name, "", oc.Unit, ("", "10"), ("7", "17"), ("7", "24")); + AssertGaugeEventsPresent(events, meter.Name, og.Name, "", og.Unit, "9", "18", "27"); + AssertHistogramEventsPresent(events, meter.Name, h.Name, "", h.Unit, ("0.5=19;0.95=19;0.99=19", "1", "19"), ("0.5=26;0.95=26;0.99=26", "1", "26")); + AssertUpDownCounterEventsPresent(events, meter.Name, udc.Name, "", udc.Unit, ("33", "33"), ("40", "73")); + AssertUpDownCounterEventsPresent(events, meter.Name, oudc.Name, "", oudc.Unit, ("", "11"), ("11", "22"), ("11", "33")); + AssertCollectStartStopEventsPresent(events, IntervalSecs, 3); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] + public void MultipleListeners_SharedSessionRejectsUnsharedListener() + { + using Meter meter = new Meter("TestMeter7"); + Counter c = meter.CreateCounter("counter1", "hat", "Fooz!!"); + int counterState = 3; + ObservableCounter oc = meter.CreateObservableCounter("observableCounter1", () => { counterState += 7; return counterState; }, "MB", "Size of universe"); + int gaugeState = 0; + ObservableGauge og = meter.CreateObservableGauge("observableGauge1", () => { gaugeState += 9; return gaugeState; }, "12394923 asd [],;/", "junk!"); + Histogram h = meter.CreateHistogram("histogram1", "a unit", "the description"); + UpDownCounter udc = meter.CreateUpDownCounter("upDownCounter1", "udc unit", "udc description"); + int upDownCounterState = 0; + ObservableUpDownCounter oudc = meter.CreateObservableUpDownCounter("observableUpDownCounter1", () => { upDownCounterState += 11; return upDownCounterState; }, "oudc unit", "oudc description"); + + EventWrittenEventArgs[] events; + using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, isShared: true, IntervalSecs, "TestMeter7")) + { + listener.WaitForCollectionStop(s_waitForEventTimeout, 1); + c.Add(5); + h.Record(19); + udc.Add(33); + listener.WaitForCollectionStop(s_waitForEventTimeout, 2); + c.Add(12); + h.Record(26); + udc.Add(40); + + using (MetricsEventListener listener2 = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, IntervalSecs, "TestMeter7")) + { + listener2.WaitForMultipleSessionsNotSupportedError(s_waitForEventTimeout); + } + + listener.WaitForCollectionStop(s_waitForEventTimeout, 3); + events = listener.Events.ToArray(); + } + + AssertBeginInstrumentReportingEventsPresent(events, c, oc, og, h, udc, oudc); + AssertCounterEventsPresent(events, meter.Name, c.Name, "", c.Unit, ("5", "5"), ("12", "17")); + AssertCounterEventsPresent(events, meter.Name, oc.Name, "", oc.Unit, ("", "10"), ("7", "17"), ("7", "24")); + AssertGaugeEventsPresent(events, meter.Name, og.Name, "", og.Unit, "9", "18", "27"); + AssertHistogramEventsPresent(events, meter.Name, h.Name, "", h.Unit, ("0.5=19;0.95=19;0.99=19", "1", "19"), ("0.5=26;0.95=26;0.99=26", "1", "26")); + AssertUpDownCounterEventsPresent(events, meter.Name, udc.Name, "", udc.Unit, ("33", "33"), ("40", "73")); + AssertUpDownCounterEventsPresent(events, meter.Name, oudc.Name, "", oudc.Unit, ("", "11"), ("11", "22"), ("11", "33")); + AssertCollectStartStopEventsPresent(events, IntervalSecs, 3); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] + public void MultipleListeners_SharedSessionRejectsListenerWithDifferentArgs() + { + using Meter meter = new Meter("TestMeter7"); + Counter c = meter.CreateCounter("counter1", "hat", "Fooz!!"); + + EventWrittenEventArgs[] events, events2; + using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, isShared: true, IntervalSecs, 10, 12, "TestMeter7")) + { + listener.WaitForCollectionStop(s_waitForEventTimeout, 1); + c.Add(5); + listener.WaitForCollectionStop(s_waitForEventTimeout, 2); + c.Add(12); + + using (MetricsEventListener listener2 = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, isShared: true, IntervalSecs, 11, 13, "TestMeter7")) + { + listener2.WaitForMultipleSessionsConfiguredIncorrectlyError(s_waitForEventTimeout); + events2 = listener2.Events.ToArray(); + AssertMultipleSessionsConfiguredIncorrectlyErrorEventsPresent(events2, "12", "13", "10", "11", IntervalSecs.ToString(), IntervalSecs.ToString()); + } + + listener.WaitForCollectionStop(s_waitForEventTimeout, 3); + c.Add(19); + listener.WaitForCollectionStop(s_waitForEventTimeout, 4); + events = listener.Events.ToArray(); + } + + AssertBeginInstrumentReportingEventsPresent(events, c); + AssertCounterEventsPresent(events, meter.Name, c.Name, "", c.Unit, ("5", "5"), ("12", "17"), ("19", "36")); + AssertCollectStartStopEventsPresent(events, IntervalSecs, 4); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] + [ActiveIssue("This test appears to interfere with the others due to the session not being shut down.")] + public void MultipleListeners_SharedSessionWithoutClientIdRejectsSharedListenerWithDifferentArgsAfterListenerDisposed() + { + using Meter meter = new Meter("TestMeter7"); + Counter c = meter.CreateCounter("counter1", "hat", "Fooz!!"); + + EventWrittenEventArgs[] events, events2; + using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, string.Empty, isShared: true, IntervalSecs, 10, 12, "TestMeter7")) + { + listener.WaitForCollectionStop(s_waitForEventTimeout, 1); + c.Add(5); + listener.WaitForCollectionStop(s_waitForEventTimeout, 2); + c.Add(12); + listener.WaitForCollectionStop(s_waitForEventTimeout, 3); + c.Add(19); + listener.WaitForCollectionStop(s_waitForEventTimeout, 4); + events = listener.Events.ToArray(); + } + + using (MetricsEventListener listener2 = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, isShared: true, IntervalSecs, 11, 13, "TestMeter7")) + { + listener2.WaitForMultipleSessionsConfiguredIncorrectlyError(s_waitForEventTimeout); + events2 = listener2.Events.ToArray(); + AssertMultipleSessionsConfiguredIncorrectlyErrorEventsPresent(events2, "12", "13", "10", "11", IntervalSecs.ToString(), IntervalSecs.ToString()); + } + + AssertBeginInstrumentReportingEventsPresent(events, c); + AssertCounterEventsPresent(events, meter.Name, c.Name, "", c.Unit, ("5", "5"), ("12", "17"), ("19", "36")); + AssertCollectStartStopEventsPresent(events, IntervalSecs, 4); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] + public void MultipleListeners_SharedSessionRejectsListenerWithDifferentInterval() + { + using Meter meter = new Meter("TestMeter7"); + Counter c = meter.CreateCounter("counter1", "hat", "Fooz!!"); + int counterState = 3; + ObservableCounter oc = meter.CreateObservableCounter("observableCounter1", () => { counterState += 7; return counterState; }, "MB", "Size of universe"); + int gaugeState = 0; + ObservableGauge og = meter.CreateObservableGauge("observableGauge1", () => { gaugeState += 9; return gaugeState; }, "12394923 asd [],;/", "junk!"); + Histogram h = meter.CreateHistogram("histogram1", "a unit", "the description"); + UpDownCounter udc = meter.CreateUpDownCounter("upDownCounter1", "udc unit", "udc description"); + int upDownCounterState = 0; + ObservableUpDownCounter oudc = meter.CreateObservableUpDownCounter("observableUpDownCounter1", () => { upDownCounterState += 11; return upDownCounterState; }, "oudc unit", "oudc description"); + + EventWrittenEventArgs[] events, events2; + using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, isShared: true, IntervalSecs, "TestMeter7")) + { + listener.WaitForCollectionStop(s_waitForEventTimeout, 1); + c.Add(5); + h.Record(19); + udc.Add(33); + listener.WaitForCollectionStop(s_waitForEventTimeout, 2); + c.Add(12); + h.Record(26); + udc.Add(40); + + using (MetricsEventListener listener2 = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, isShared: true, IntervalSecs + 1, "TestMeter7")) + { + listener2.WaitForMultipleSessionsConfiguredIncorrectlyError(s_waitForEventTimeout); + events2 = listener2.Events.ToArray(); + AssertMultipleSessionsConfiguredIncorrectlyErrorEventsPresent(events2, MetricsEventListener.HistogramLimit.ToString(), MetricsEventListener.HistogramLimit.ToString(), + MetricsEventListener.TimeSeriesLimit.ToString(), MetricsEventListener.TimeSeriesLimit.ToString(), IntervalSecs.ToString(), (IntervalSecs + 1).ToString()); + } + + listener.WaitForCollectionStop(s_waitForEventTimeout, 3); + events = listener.Events.ToArray(); + } + + AssertBeginInstrumentReportingEventsPresent(events, c, oc, og, h, udc, oudc); + AssertCounterEventsPresent(events, meter.Name, c.Name, "", c.Unit, ("5", "5"), ("12", "17")); + AssertCounterEventsPresent(events, meter.Name, oc.Name, "", oc.Unit, ("", "10"), ("7", "17"), ("7", "24")); + AssertGaugeEventsPresent(events, meter.Name, og.Name, "", og.Unit, "9", "18", "27"); + AssertHistogramEventsPresent(events, meter.Name, h.Name, "", h.Unit, ("0.5=19;0.95=19;0.99=19", "1", "19"), ("0.5=26;0.95=26;0.99=26", "1", "26")); + AssertUpDownCounterEventsPresent(events, meter.Name, udc.Name, "", udc.Unit, ("33", "33"), ("40", "73")); + AssertUpDownCounterEventsPresent(events, meter.Name, oudc.Name, "", oudc.Unit, ("", "11"), ("11", "22"), ("11", "33")); + AssertCollectStartStopEventsPresent(events, IntervalSecs, 3); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] + public void MultipleListeners_DisposeMeterBeforeSecondListener() + { + using Meter meterA = new Meter("TestMeter8"); + using Meter meterB = new Meter("TestMeter9"); + Counter c = meterA.CreateCounter("counter1", "hat", "Fooz!!"); + int counterState = 3; + ObservableCounter oc = meterA.CreateObservableCounter("observableCounter1", () => { counterState += 7; return counterState; }, "MB", "Size of universe"); + int gaugeState = 0; + ObservableGauge og = meterA.CreateObservableGauge("observableGauge1", () => { gaugeState += 9; return gaugeState; }, "12394923 asd [],;/", "junk!"); + Histogram h = meterB.CreateHistogram("histogram1", "a unit", "the description"); + UpDownCounter udc = meterA.CreateUpDownCounter("upDownCounter1", "udc unit", "udc description"); + int upDownCounterState = 0; + ObservableUpDownCounter oudc = meterA.CreateObservableUpDownCounter("observableUpDownCounter1", () => { upDownCounterState += 11; return upDownCounterState; }, "oudc unit", "oudc description"); + + EventWrittenEventArgs[] events, events2; + using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, isShared: true, IntervalSecs, "TestMeter8;TestMeter9")) + { + listener.WaitForCollectionStop(s_waitForEventTimeout, 1); + c.Add(5); + h.Record(19); + udc.Add(33); + listener.WaitForCollectionStop(s_waitForEventTimeout, 2); + c.Add(12); + h.Record(26); + udc.Add(40); + listener.WaitForCollectionStop(s_waitForEventTimeout, 3); + + meterA.Dispose(); + listener.WaitForEndInstrumentReporting(s_waitForEventTimeout, 3); + + using (MetricsEventListener listener2 = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, isShared: true, IntervalSecs, "TestMeter8")) + { + events2 = listener2.Events.ToArray(); + } + + h.Record(21); + listener.WaitForCollectionStop(s_waitForEventTimeout, 4); + events = listener.Events.ToArray(); + } + + AssertBeginInstrumentReportingEventsPresent(events, c, oc, og, h, udc, oudc, h); // only h occurs twice because meterA is disposed before listener2 is created + AssertBeginInstrumentReportingEventsPresent(events2, h); + AssertInitialEnumerationCompleteEventPresent(events, 2); + AssertInitialEnumerationCompleteEventPresent(events2); + AssertCounterEventsPresent(events, meterA.Name, c.Name, "", c.Unit, ("5", "5"), ("12", "17")); + AssertCounterEventsPresent(events, meterA.Name, oc.Name, "", oc.Unit, ("", "10"), ("7", "17"), ("7", "24")); + AssertGaugeEventsPresent(events, meterA.Name, og.Name, "", og.Unit, "9", "18", "27"); + AssertHistogramEventsPresent(events, meterB.Name, h.Name, "", h.Unit, ("0.5=19;0.95=19;0.99=19", "1", "19"), ("0.5=26;0.95=26;0.99=26", "1", "26"), ("0.5=21;0.95=21;0.99=21", "1", "21")); + AssertUpDownCounterEventsPresent(events, meterA.Name, udc.Name, "", udc.Unit, ("33", "33"), ("40", "73")); + AssertUpDownCounterEventsPresent(events, meterA.Name, oudc.Name, "", oudc.Unit, ("", "11"), ("11", "22"), ("11", "33")); + AssertCollectStartStopEventsPresent(events, IntervalSecs, 4); + AssertEndInstrumentReportingEventsPresent(events, c, oc, og, udc, oudc); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] + public void MultipleListeners_DisposeMetersDuringAndAfterSecondListener() + { + using Meter meterA = new Meter("TestMeter8"); + using Meter meterB = new Meter("TestMeter9"); + Counter c = meterA.CreateCounter("counter1", "hat", "Fooz!!"); + int counterState = 3; + ObservableCounter oc = meterA.CreateObservableCounter("observableCounter1", () => { counterState += 7; return counterState; }, "MB", "Size of universe"); + int gaugeState = 0; + ObservableGauge og = meterA.CreateObservableGauge("observableGauge1", () => { gaugeState += 9; return gaugeState; }, "12394923 asd [],;/", "junk!"); + Histogram h = meterB.CreateHistogram("histogram1", "a unit", "the description"); + UpDownCounter udc = meterA.CreateUpDownCounter("upDownCounter1", "udc unit", "udc description"); + int upDownCounterState = 0; + ObservableUpDownCounter oudc = meterA.CreateObservableUpDownCounter("observableUpDownCounter1", () => { upDownCounterState += 11; return upDownCounterState; }, "oudc unit", "oudc description"); + + EventWrittenEventArgs[] events, events2; + using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, isShared: true, IntervalSecs, "TestMeter8;TestMeter9")) + { + listener.WaitForCollectionStop(s_waitForEventTimeout, 1); + c.Add(5); + h.Record(19); + udc.Add(33); + listener.WaitForCollectionStop(s_waitForEventTimeout, 2); + c.Add(12); + h.Record(26); + udc.Add(40); + listener.WaitForCollectionStop(s_waitForEventTimeout, 3); + + using (MetricsEventListener listener2 = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, isShared: true, IntervalSecs, "TestMeter8;TestMeter9")) + { + meterA.Dispose(); + listener.WaitForEndInstrumentReporting(s_waitForEventTimeout, 3); + + events2 = listener2.Events.ToArray(); + } + + h.Record(21); + listener.WaitForCollectionStop(s_waitForEventTimeout, 4); + + meterB.Dispose(); + listener.WaitForEndInstrumentReporting(s_waitForEventTimeout, 5); + + events = listener.Events.ToArray(); + } + + AssertBeginInstrumentReportingEventsPresent(events, c, oc, og, h, udc, oudc, c, oc, og, h, udc, oudc); + AssertBeginInstrumentReportingEventsPresent(events2, c, oc, og, h, udc, oudc); + AssertInitialEnumerationCompleteEventPresent(events, 2); + AssertInitialEnumerationCompleteEventPresent(events2); + AssertCounterEventsPresent(events, meterA.Name, c.Name, "", c.Unit, ("5", "5"), ("12", "17")); + AssertCounterEventsPresent(events, meterA.Name, oc.Name, "", oc.Unit, ("", "10"), ("7", "17"), ("7", "24")); + AssertGaugeEventsPresent(events, meterA.Name, og.Name, "", og.Unit, "9", "18", "27"); + AssertHistogramEventsPresent(events, meterB.Name, h.Name, "", h.Unit, ("0.5=19;0.95=19;0.99=19", "1", "19"), ("0.5=26;0.95=26;0.99=26", "1", "26"), ("0.5=21;0.95=21;0.99=21", "1", "21")); + AssertUpDownCounterEventsPresent(events, meterA.Name, udc.Name, "", udc.Unit, ("33", "33"), ("40", "73")); + AssertUpDownCounterEventsPresent(events, meterA.Name, oudc.Name, "", oudc.Unit, ("", "11"), ("11", "22"), ("11", "33")); + AssertCollectStartStopEventsPresent(events, IntervalSecs, 4); + AssertEndInstrumentReportingEventsPresent(events, c, oc, og, udc, oudc, h); + AssertEndInstrumentReportingEventsPresent(events2, c, oc, og, udc, oudc); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] + public void MultipleListeners_PublishingInstruments() + { + using Meter meterA = new Meter("TestMeter10"); + using Meter meterB = new Meter("TestMeter11"); + Counter c = meterA.CreateCounter("counter1", "hat", "Fooz!!"); + int counterState = 3; + ObservableCounter oc = meterA.CreateObservableCounter("observableCounter1", () => { counterState += 7; return counterState; }, "MB", "Size of universe"); + int gaugeState = 0; + ObservableGauge og = meterA.CreateObservableGauge("observableGauge1", () => { gaugeState += 9; return gaugeState; }, "12394923 asd [],;/", "junk!"); + Histogram h = meterB.CreateHistogram("histogram1", "a unit", "the description"); + UpDownCounter udc = meterA.CreateUpDownCounter("upDownCounter1", "udc unit", "udc description"); + int upDownCounterState = 0; + ObservableUpDownCounter oudc = meterA.CreateObservableUpDownCounter("observableUpDownCounter1", () => { upDownCounterState += 11; return upDownCounterState; }, "oudc unit", "oudc description"); + + EventWrittenEventArgs[] events, events2; + using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.InstrumentPublishing, isShared: true, null, "")) + { + listener.WaitForEnumerationComplete(s_waitForEventTimeout); + using (MetricsEventListener listener2 = new MetricsEventListener(_output, MetricsEventListener.InstrumentPublishing, isShared: true, null, "")) + { + listener2.WaitForEnumerationComplete(s_waitForEventTimeout); + events = listener.Events.ToArray(); + events2 = listener2.Events.ToArray(); + } + } + + AssertInstrumentPublishingEventsPresent(events, c, oc, og, h, udc, oudc, c, oc, og, h, udc, oudc); + AssertInitialEnumerationCompleteEventPresent(events, 2); + AssertInstrumentPublishingEventsPresent(events2, c, oc, og, h, udc, oudc); + AssertInitialEnumerationCompleteEventPresent(events2); + } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] [OuterLoop("Slow and has lots of console spew")] public void EventSourcePublishesTimeSeriesWithEmptyMetadata() @@ -447,52 +1078,6 @@ public void EventSourcePublishesMissingDataPoints() AssertCollectStartStopEventsPresent(events, IntervalSecs, 5); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] - [OuterLoop("Slow and has lots of console spew")] - public void EventSourceRejectsNewListener() - { - using Meter meter = new Meter("TestMeter7"); - Counter c = meter.CreateCounter("counter1", "hat", "Fooz!!"); - int counterState = 3; - ObservableCounter oc = meter.CreateObservableCounter("observableCounter1", () => { counterState += 7; return counterState; }, "MB", "Size of universe"); - int gaugeState = 0; - ObservableGauge og = meter.CreateObservableGauge("observableGauge1", () => { gaugeState += 9; return gaugeState; }, "12394923 asd [],;/", "junk!"); - Histogram h = meter.CreateHistogram("histogram1", "a unit", "the description"); - UpDownCounter udc = meter.CreateUpDownCounter("upDownCounter1", "udc unit", "udc description"); - int upDownCounterState = 0; - ObservableUpDownCounter oudc = meter.CreateObservableUpDownCounter("observableUpDownCounter1", () => { upDownCounterState += 11; return upDownCounterState; }, "oudc unit", "oudc description"); - - EventWrittenEventArgs[] events; - using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, IntervalSecs, "TestMeter7")) - { - listener.WaitForCollectionStop(s_waitForEventTimeout, 1); - c.Add(5); - h.Record(19); - udc.Add(33); - listener.WaitForCollectionStop(s_waitForEventTimeout, 2); - c.Add(12); - h.Record(26); - udc.Add(40); - - // some alternate listener attempts to listen in the middle - using MetricsEventListener listener2 = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, IntervalSecs, "ADifferentMeter"); - listener2.WaitForMultipleSessionsNotSupportedError(s_waitForEventTimeout); - - - listener.WaitForCollectionStop(s_waitForEventTimeout, 3); - events = listener.Events.ToArray(); - } - - AssertBeginInstrumentReportingEventsPresent(events, c, oc, og, h, udc, oudc); - AssertCounterEventsPresent(events, meter.Name, c.Name, "", c.Unit, ("5", "5"), ("12", "17")); - AssertCounterEventsPresent(events, meter.Name, oc.Name, "", oc.Unit, ("", "10"), ("7", "17"), ("7", "24")); - AssertGaugeEventsPresent(events, meter.Name, og.Name, "", og.Unit, "9", "18", "27"); - AssertHistogramEventsPresent(events, meter.Name, h.Name, "", h.Unit, ("0.5=19;0.95=19;0.99=19", "1", "19"), ("0.5=26;0.95=26;0.99=26", "1", "26")); - AssertUpDownCounterEventsPresent(events, meter.Name, udc.Name, "", udc.Unit, ("33", "33"), ("40", "73")); - AssertUpDownCounterEventsPresent(events, meter.Name, oudc.Name, "", oudc.Unit, ("", "11"), ("11", "22"), ("11", "33")); - AssertCollectStartStopEventsPresent(events, IntervalSecs, 3); - } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] [OuterLoop("Slow and has lots of console spew")] public void EventSourcePublishesEndEventsOnMeterDispose() @@ -844,7 +1429,6 @@ public void EventSourceEnforcesHistogramLimitAndNotMaxTimeSeries() AssertCollectStartStopEventsPresent(events, IntervalSecs, 3); } - private void AssertBeginInstrumentReportingEventsPresent(EventWrittenEventArgs[] events, params Instrument[] expectedInstruments) { var beginReportEvents = events.Where(e => e.EventName == "BeginInstrumentReporting").Select(e => @@ -897,9 +1481,9 @@ private void AssertEndInstrumentReportingEventsPresent(EventWrittenEventArgs[] e Assert.Equal(expectedInstruments.Length, beginReportEvents.Length); } - private void AssertInitialEnumerationCompleteEventPresent(EventWrittenEventArgs[] events) + private void AssertInitialEnumerationCompleteEventPresent(EventWrittenEventArgs[] events, int eventsCount = 1) { - Assert.Equal(1, events.Where(e => e.EventName == "InitialInstrumentEnumerationComplete").Count()); + Assert.Equal(eventsCount, events.Where(e => e.EventName == "InitialInstrumentEnumerationComplete").Count()); } private void AssertTimeSeriesLimitPresent(EventWrittenEventArgs[] events) @@ -1093,35 +1677,72 @@ private void AssertObservableCallbackErrorPresent(EventWrittenEventArgs[] events Assert.NotEmpty(errorEvents); Assert.Contains("Example user exception", errorEvents[0].ErrorText); } + + private void AssertMultipleSessionsConfiguredIncorrectlyErrorEventsPresent(EventWrittenEventArgs[] events, + string expectedMaxHistograms, string actualMaxHistograms, string expectedMaxTimeSeries, string actualMaxTimeSeries, + string expectedRefreshInterval, string actualRefreshInterval) + { + var counterEvents = events.Where(e => e.EventName == "MultipleSessionsConfiguredIncorrectlyError").Select(e => + new + { + ExpectedMaxHistograms = e.Payload[1].ToString(), + ActualMaxHistograms = e.Payload[2].ToString(), + ExpectedMaxTimeSeries = e.Payload[3].ToString(), + ActualMaxTimeSeries = e.Payload[4].ToString(), + ExpectedRefreshInterval = e.Payload[5].ToString(), + ActualRefreshInterval = e.Payload[6].ToString(), + }).ToArray(); + var filteredEvents = counterEvents; + Assert.Single(filteredEvents); + + Assert.Equal(expectedMaxHistograms, filteredEvents[0].ExpectedMaxHistograms); + Assert.Equal(expectedMaxTimeSeries, filteredEvents[0].ExpectedMaxTimeSeries); + Assert.Equal(expectedRefreshInterval, filteredEvents[0].ExpectedRefreshInterval); + Assert.Equal(actualMaxHistograms, filteredEvents[0].ActualMaxHistograms); + Assert.Equal(actualMaxTimeSeries, filteredEvents[0].ActualMaxTimeSeries); + Assert.Equal(actualRefreshInterval, filteredEvents[0].ActualRefreshInterval); + } } class MetricsEventListener : EventListener { - public const EventKeywords MessagesKeyword = (EventKeywords)0x1; public const EventKeywords TimeSeriesValues = (EventKeywords)0x2; public const EventKeywords InstrumentPublishing = (EventKeywords)0x4; - + public const int TimeSeriesLimit = 50; + public const int HistogramLimit = 50; + public const string SharedSessionId = "SHARED"; public MetricsEventListener(ITestOutputHelper output, EventKeywords keywords, double? refreshInterval, params string[]? instruments) : - this(output, keywords, Guid.NewGuid().ToString(), refreshInterval, 50, 50, instruments) + this(output, keywords, refreshInterval, TimeSeriesLimit, HistogramLimit, instruments) + { + } + + public MetricsEventListener(ITestOutputHelper output, EventKeywords keywords, bool isShared, double? refreshInterval, params string[]? instruments) : + this(output, keywords, isShared, refreshInterval, TimeSeriesLimit, HistogramLimit, instruments) { } public MetricsEventListener(ITestOutputHelper output, EventKeywords keywords, double? refreshInterval, int timeSeriesLimit, int histogramLimit, params string[]? instruments) : - this(output, keywords, Guid.NewGuid().ToString(), refreshInterval, timeSeriesLimit, histogramLimit, instruments) + this(output, keywords, false, refreshInterval, timeSeriesLimit, histogramLimit, instruments) + { + } + + public MetricsEventListener(ITestOutputHelper output, EventKeywords keywords, bool isShared, double? refreshInterval, + int timeSeriesLimit, int histogramLimit, params string[]? instruments) : + this(output, keywords, Guid.NewGuid().ToString(), isShared, refreshInterval, timeSeriesLimit, histogramLimit, instruments) { } - public MetricsEventListener(ITestOutputHelper output, EventKeywords keywords, string sessionId, double? refreshInterval, + public MetricsEventListener(ITestOutputHelper output, EventKeywords keywords, string sessionId, bool isShared, double? refreshInterval, int timeSeriesLimit, int histogramLimit, params string[]? instruments) : - this(output, keywords, sessionId, - FormatArgDictionary(refreshInterval,timeSeriesLimit, histogramLimit, instruments, sessionId)) + this(output, keywords, sessionId, isShared, + FormatArgDictionary(refreshInterval,timeSeriesLimit, histogramLimit, instruments, sessionId, isShared)) { } - private static Dictionary FormatArgDictionary(double? refreshInterval, int? timeSeriesLimit, int? histogramLimit, string?[]? instruments, string? sessionId) + private static Dictionary FormatArgDictionary(double? refreshInterval, int? timeSeriesLimit, int? histogramLimit, string?[]? instruments, string? sessionId, bool shared) { Dictionary d = new Dictionary(); if(instruments != null) @@ -1134,7 +1755,15 @@ private static Dictionary FormatArgDictionary(double? refreshInte } if(sessionId != null) { - d.Add("SessionId", sessionId); + if (shared) + { + d.Add("SessionId", SharedSessionId); + d.Add("ClientId", sessionId); + } + else + { + d.Add("SessionId", sessionId); + } } if(timeSeriesLimit != null) { @@ -1144,14 +1773,15 @@ private static Dictionary FormatArgDictionary(double? refreshInte { d.Add("MaxHistograms", histogramLimit.ToString()); } + return d; } - public MetricsEventListener(ITestOutputHelper output, EventKeywords keywords, string sessionId, Dictionary arguments) + public MetricsEventListener(ITestOutputHelper output, EventKeywords keywords, string sessionId, bool shared, Dictionary arguments) { _output = output; _keywords = keywords; - _sessionId = sessionId; + _sessionId = shared ? SharedSessionId : sessionId; _arguments = arguments; if (_source != null) { @@ -1196,7 +1826,7 @@ public override void Dispose() protected override void OnEventWritten(EventWrittenEventArgs eventData) { string sessionId = eventData.Payload[0].ToString(); - if (eventData.EventName != "MultipleSessionsNotSupportedError" && sessionId != "" && sessionId != _sessionId) + if (eventData.EventName != "MultipleSessionsNotSupportedError" && eventData.EventName != "MultipleSessionsConfiguredIncorrectlyError" && sessionId != "" && sessionId != _sessionId) { return; } @@ -1228,6 +1858,8 @@ protected override void OnEventWritten(EventWrittenEventArgs eventData) public void WaitForMultipleSessionsNotSupportedError(TimeSpan timeout) => WaitForEvent(timeout, 1, "MultipleSessionsNotSupportedError"); + public void WaitForMultipleSessionsConfiguredIncorrectlyError(TimeSpan timeout) => WaitForEvent(timeout, 1, "MultipleSessionsConfiguredIncorrectlyError"); + void WaitForEvent(TimeSpan timeout, int numEvents, string eventName) { DateTime startTime = DateTime.Now;