From 9e23387ca07782ea2bff365bf28be8a342e09724 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed <10833894+tarekgh@users.noreply.github.com> Date: Sun, 21 Jul 2024 16:57:40 -0700 Subject: [PATCH] Emit More EventSource Data For Metrics Measurements (#104993) * Emit More EventSource Data For Metrics Measurements * rename event param names for consistency * Update src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Helpers.cs Co-authored-by: Stephen Toub * Use int for meter and instrument hashes * Reduce the data emitted with metric value publishing --------- Co-authored-by: Stephen Toub --- ...System.Diagnostics.DiagnosticSource.csproj | 1 + .../src/System/Diagnostics/Helpers.cs | 60 +++++++ .../Diagnostics/Metrics/AggregationManager.cs | 25 +-- .../Diagnostics/Metrics/InstrumentState.cs | 8 +- .../src/System/Diagnostics/Metrics/Meter.cs | 1 + .../Diagnostics/Metrics/MetricsEventSource.cs | 148 ++++++------------ .../tests/MetricEventSourceTests.cs | 120 +++++++++----- ....Diagnostics.DiagnosticSource.Tests.csproj | 1 + 8 files changed, 218 insertions(+), 146 deletions(-) create mode 100644 src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Helpers.cs diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj index ed5e35e4f094a..62a37ab56e29f 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj @@ -49,6 +49,7 @@ System.Diagnostics.DiagnosticSource + diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Helpers.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Helpers.cs new file mode 100644 index 0000000000000..874b61de6958e --- /dev/null +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Helpers.cs @@ -0,0 +1,60 @@ +// 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.Globalization; +using System.Runtime.CompilerServices; +using System.Text; + +namespace System.Diagnostics +{ + internal static class Helpers + { + internal static string FormatTags(IEnumerable>? tags) + { + if (tags is null) + { + return string.Empty; + } + + StringBuilder sb = new StringBuilder(); + bool first = true; + foreach (KeyValuePair tag in tags) + { + if (first) + { + first = false; + } + else + { + sb.Append(','); + } + + sb.Append(tag.Key).Append('=').Append(tag.Value); + } + return sb.ToString(); + } + + internal static string FormatTags(KeyValuePair[] labels) + { + if (labels is null || labels.Length == 0) + { + return string.Empty; + } + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < labels.Length; i++) + { + sb.Append(labels[i].Key).Append('=').Append(labels[i].Value); + if (i != labels.Length - 1) + { + sb.Append(','); + } + } + return sb.ToString(); + } + + internal static string FormatObjectHash(object? obj) => + obj is null ? string.Empty : RuntimeHelpers.GetHashCode(obj).ToString(CultureInfo.InvariantCulture); + } +} 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 34ecde9bf1a26..3c7090d5d6d95 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 @@ -32,12 +32,12 @@ internal sealed class AggregationManager private readonly MeterListener _listener; private int _currentTimeSeries; private int _currentHistograms; - private readonly Action _collectMeasurement; + private readonly Action _collectMeasurement; private readonly Action _beginCollection; private readonly Action _endCollection; - private readonly Action _beginInstrumentMeasurements; - private readonly Action _endInstrumentMeasurements; - private readonly Action _instrumentPublished; + private readonly Action _beginInstrumentMeasurements; + private readonly Action _endInstrumentMeasurements; + private readonly Action _instrumentPublished; private readonly Action _initialInstrumentEnumerationComplete; private readonly Action _collectionError; private readonly Action _timeSeriesLimitReached; @@ -47,12 +47,12 @@ internal sealed class AggregationManager public AggregationManager( int maxTimeSeries, int maxHistograms, - Action collectMeasurement, + Action collectMeasurement, Action beginCollection, Action endCollection, - Action beginInstrumentMeasurements, - Action endInstrumentMeasurements, - Action instrumentPublished, + Action beginInstrumentMeasurements, + Action endInstrumentMeasurements, + Action instrumentPublished, Action initialInstrumentEnumerationComplete, Action collectionError, Action timeSeriesLimitReached, @@ -118,17 +118,18 @@ public AggregationManager SetCollectionPeriod(TimeSpan collectionPeriod) private void CompletedMeasurements(Instrument instrument, object? cookie) { _instruments.Remove(instrument); - _endInstrumentMeasurements(instrument); + Debug.Assert(cookie is not null); + _endInstrumentMeasurements(instrument, (InstrumentState)cookie); RemoveInstrumentState(instrument); } private void PublishedInstrument(Instrument instrument, MeterListener _) { - _instrumentPublished(instrument); InstrumentState? state = GetInstrumentState(instrument); + _instrumentPublished(instrument, state); if (state != null) { - _beginInstrumentMeasurements(instrument); + _beginInstrumentMeasurements(instrument, state); #pragma warning disable CA1864 // Prefer the 'IDictionary.TryAdd(TKey, TValue)' method. IDictionary.TryAdd() is not available in one of the builds if (!_instruments.ContainsKey(instrument)) #pragma warning restore CA1864 @@ -418,7 +419,7 @@ internal void Collect() { kv.Value.Collect(kv.Key, (LabeledAggregationStatistics labeledAggStats) => { - _collectMeasurement(kv.Key, labeledAggStats); + _collectMeasurement(kv.Key, labeledAggStats, kv.Value); }); } } diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/InstrumentState.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/InstrumentState.cs index fa1b2612aad9a..c67c609ac0517 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/InstrumentState.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/InstrumentState.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Security; +using System.Threading; namespace System.Diagnostics.Metrics { @@ -14,16 +15,19 @@ internal abstract class InstrumentState // This can be called concurrently with Update() public abstract void Collect(Instrument instrument, Action aggregationVisitFunc); - } + public abstract int ID { get; } + } internal sealed class InstrumentState : InstrumentState where TAggregator : Aggregator { private AggregatorStore _aggregatorStore; + private static int s_idCounter; public InstrumentState(Func createAggregatorFunc) { + ID = Interlocked.Increment(ref s_idCounter); _aggregatorStore = new AggregatorStore(createAggregatorFunc); } @@ -38,5 +42,7 @@ public override void Update(double measurement, ReadOnlySpan s_allMeters = new List(); private List _instruments = new List(); private Dictionary> _nonObservableInstrumentsCache = new(); + internal bool Disposed { get; private set; } internal static bool IsSupported { get; } = InitializeIsSupported(); 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 ba789aa98b15b..c3ac57bb310f3 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 @@ -115,40 +115,40 @@ public void CollectionStop(string sessionId, DateTime intervalStartTime, DateTim WriteEvent(3, sessionId, intervalStartTime, intervalEndTime); } - [Event(4, Keywords = Keywords.TimeSeriesValues, Version = 1)] + [Event(4, Keywords = Keywords.TimeSeriesValues, Version = 2)] #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 CounterRateValuePublished(string sessionId, string meterName, string? meterVersion, string instrumentName, string? unit, string tags, string rate, string value) + public void CounterRateValuePublished(string sessionId, string meterName, string? meterVersion, string instrumentName, string? unit, string tags, string rate, string value, int instrumentId) { - WriteEvent(4, sessionId, meterName, meterVersion ?? "", instrumentName, unit ?? "", tags, rate, value); + WriteEvent(4, sessionId, meterName, meterVersion ?? "", instrumentName, unit ?? "", tags, rate, value, instrumentId); } - [Event(5, Keywords = Keywords.TimeSeriesValues)] + [Event(5, Keywords = Keywords.TimeSeriesValues, Version = 2)] #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 GaugeValuePublished(string sessionId, string meterName, string? meterVersion, string instrumentName, string? unit, string tags, string lastValue) + public void GaugeValuePublished(string sessionId, string meterName, string? meterVersion, string instrumentName, string? unit, string tags, string lastValue, int instrumentId) { - WriteEvent(5, sessionId, meterName, meterVersion ?? "", instrumentName, unit ?? "", tags, lastValue); + WriteEvent(5, sessionId, meterName, meterVersion ?? "", instrumentName, unit ?? "", tags, lastValue, instrumentId); } - [Event(6, Keywords = Keywords.TimeSeriesValues, Version = 1)] + [Event(6, Keywords = Keywords.TimeSeriesValues, Version = 2)] #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 HistogramValuePublished(string sessionId, string meterName, string? meterVersion, string instrumentName, string? unit, string tags, string quantiles, int count, double sum) + public void HistogramValuePublished(string sessionId, string meterName, string? meterVersion, string instrumentName, string? unit, string tags, string quantiles, int count, double sum, int instrumentId) { - WriteEvent(6, sessionId, meterName, meterVersion ?? "", instrumentName, unit ?? "", tags, quantiles, count, sum); + WriteEvent(6, sessionId, meterName, meterVersion ?? "", instrumentName, unit ?? "", tags, quantiles, count, sum, instrumentId); } // Sent when we begin to monitor the value of a instrument, either because new session filter arguments changed subscriptions // or because an instrument matching the pre-existing filter has just been created. This event precedes all *MetricPublished events // for the same named instrument. - [Event(7, Keywords = Keywords.TimeSeriesValues, Version = 1)] + [Event(7, Keywords = Keywords.TimeSeriesValues, Version = 2)] #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.")] @@ -163,15 +163,16 @@ public void BeginInstrumentReporting( string? description, string instrumentTags, string meterTags, - string meterScopeHash) + string meterScopeHash, + int instrumentId) { WriteEvent(7, sessionId, meterName, meterVersion ?? "", instrumentName, instrumentType, unit ?? "", description ?? "", - instrumentTags, meterTags, meterScopeHash); + instrumentTags, meterTags, meterScopeHash, instrumentId); } // Sent when we stop monitoring the value of a instrument, either because new session filter arguments changed subscriptions // or because the Meter has been disposed. - [Event(8, Keywords = Keywords.TimeSeriesValues, Version = 1)] + [Event(8, Keywords = Keywords.TimeSeriesValues, Version = 2)] #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.")] @@ -186,10 +187,11 @@ public void EndInstrumentReporting( string? description, string instrumentTags, string meterTags, - string meterScopeHash) + string meterScopeHash, + int instrumentId) { WriteEvent(8, sessionId, meterName, meterVersion ?? "", instrumentName, instrumentType, unit ?? "", description ?? "", - instrumentTags, meterTags, meterScopeHash); + instrumentTags, meterTags, meterScopeHash, instrumentId); } [Event(9, Keywords = Keywords.TimeSeriesValues | Keywords.Messages | Keywords.InstrumentPublishing)] @@ -204,7 +206,7 @@ public void InitialInstrumentEnumerationComplete(string sessionId) WriteEvent(10, sessionId); } - [Event(11, Keywords = Keywords.InstrumentPublishing, Version = 1)] + [Event(11, Keywords = Keywords.InstrumentPublishing, Version = 2)] #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.")] @@ -219,10 +221,11 @@ public void InstrumentPublished( string? description, string instrumentTags, string meterTags, - string meterScopeHash) + string meterScopeHash, + int instrumentId) { WriteEvent(11, sessionId, meterName, meterVersion ?? "", instrumentName, instrumentType, unit ?? "", description ?? "", - instrumentTags, meterTags, meterScopeHash); + instrumentTags, meterTags, meterScopeHash, instrumentId); } [Event(12, Keywords = Keywords.TimeSeriesValues)] @@ -249,14 +252,14 @@ public void MultipleSessionsNotSupportedError(string runningSessionId) WriteEvent(15, runningSessionId); } - [Event(16, Keywords = Keywords.TimeSeriesValues, Version = 1)] + [Event(16, Keywords = Keywords.TimeSeriesValues, Version = 2)] #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 UpDownCounterRateValuePublished(string sessionId, string meterName, string? meterVersion, string instrumentName, string? unit, string tags, string rate, string value) + public void UpDownCounterRateValuePublished(string sessionId, string meterName, string? meterVersion, string instrumentName, string? unit, string tags, string rate, string value, int instrumentId) { - WriteEvent(16, sessionId, meterName, meterVersion ?? "", instrumentName, unit ?? "", tags, rate, value); + WriteEvent(16, sessionId, meterName, meterVersion ?? "", instrumentName, unit ?? "", tags, rate, value, instrumentId); } [Event(17, Keywords = Keywords.TimeSeriesValues)] @@ -413,8 +416,8 @@ public void OnEventCommand(EventCommandEventArgs command) } } } - if ((command.Command == EventCommand.Update || command.Command == EventCommand.Enable) && - command.Arguments != null) + + if ((command.Command == EventCommand.Update || command.Command == EventCommand.Enable) && command.Arguments != null) { IncrementRefCount(commandSessionId, command); @@ -432,22 +435,22 @@ public void OnEventCommand(EventCommandEventArgs command) string sessionId = _sessionId; _aggregationManager = new AggregationManager( - maxTimeSeries, - maxHistograms, - (i, s) => TransmitMetricValue(i, s, sessionId), - (startIntervalTime, endIntervalTime) => Parent.CollectionStart(sessionId, startIntervalTime, endIntervalTime), - (startIntervalTime, endIntervalTime) => Parent.CollectionStop(sessionId, startIntervalTime, endIntervalTime), - i => Parent.BeginInstrumentReporting(sessionId, i.Meter.Name, i.Meter.Version, i.Name, i.GetType().Name, i.Unit, i.Description, - FormatTags(i.Tags), FormatTags(i.Meter.Tags), FormatScopeHash(i.Meter.Scope)), - i => Parent.EndInstrumentReporting(sessionId, i.Meter.Name, i.Meter.Version, i.Name, i.GetType().Name, i.Unit, i.Description, - FormatTags(i.Tags), FormatTags(i.Meter.Tags), FormatScopeHash(i.Meter.Scope)), - i => Parent.InstrumentPublished(sessionId, i.Meter.Name, i.Meter.Version, i.Name, i.GetType().Name, i.Unit, i.Description, - FormatTags(i.Tags), FormatTags(i.Meter.Tags), FormatScopeHash(i.Meter.Scope)), - () => Parent.InitialInstrumentEnumerationComplete(sessionId), - e => Parent.Error(sessionId, e.ToString()), - () => Parent.TimeSeriesLimitReached(sessionId), - () => Parent.HistogramLimitReached(sessionId), - e => Parent.ObservableInstrumentCallbackError(sessionId, e.ToString())); + maxTimeSeries: maxTimeSeries, + maxHistograms: maxHistograms, + collectMeasurement: (i, s, state) => TransmitMetricValue(i, s, sessionId, state), + beginCollection: (startIntervalTime, endIntervalTime) => Parent.CollectionStart(sessionId, startIntervalTime, endIntervalTime), + endCollection: (startIntervalTime, endIntervalTime) => Parent.CollectionStop(sessionId, startIntervalTime, endIntervalTime), + beginInstrumentMeasurements: (i, state) => Parent.BeginInstrumentReporting(sessionId, i.Meter.Name, i.Meter.Version, i.Name, i.GetType().Name, i.Unit, i.Description, + Helpers.FormatTags(i.Tags), Helpers.FormatTags(i.Meter.Tags), Helpers.FormatObjectHash(i.Meter.Scope), state.ID), + endInstrumentMeasurements: (i, state) => Parent.EndInstrumentReporting(sessionId, i.Meter.Name, i.Meter.Version, i.Name, i.GetType().Name, i.Unit, i.Description, + Helpers.FormatTags(i.Tags), Helpers.FormatTags(i.Meter.Tags), Helpers.FormatObjectHash(i.Meter.Scope), state.ID), + instrumentPublished: (i, state) => Parent.InstrumentPublished(sessionId, i.Meter.Name, i.Meter.Version, i.Name, i.GetType().Name, i.Unit, i.Description, + Helpers.FormatTags(i.Tags), Helpers.FormatTags(i.Meter.Tags), Helpers.FormatObjectHash(i.Meter.Scope), state is null ? 0 : state.ID), + initialInstrumentEnumerationComplete: () => Parent.InitialInstrumentEnumerationComplete(sessionId), + collectionError: e => Parent.Error(sessionId, e.ToString()), + timeSeriesLimitReached: () => Parent.TimeSeriesLimitReached(sessionId), + histogramLimitReached: () => Parent.HistogramLimitReached(sessionId), + observableInstrumentCallbackError: e => Parent.ObservableInstrumentCallbackError(sessionId, e.ToString())); _aggregationManager.SetCollectionPeriod(TimeSpan.FromSeconds(refreshIntervalSecs)); @@ -657,79 +660,32 @@ private void ParseSpecs(string? metricsSpecs) } } - private static void TransmitMetricValue(Instrument instrument, LabeledAggregationStatistics stats, string sessionId) + private static void TransmitMetricValue(Instrument instrument, LabeledAggregationStatistics stats, string sessionId, InstrumentState? instrumentState) { + int instrumentId = instrumentState?.ID ?? 0; if (stats.AggregationStatistics is CounterStatistics rateStats) { if (rateStats.IsMonotonic) { - Log.CounterRateValuePublished(sessionId, instrument.Meter.Name, instrument.Meter.Version, instrument.Name, instrument.Unit, FormatTags(stats.Labels), - rateStats.Delta.HasValue ? rateStats.Delta.Value.ToString(CultureInfo.InvariantCulture) : "", - rateStats.Value.ToString(CultureInfo.InvariantCulture)); + Log.CounterRateValuePublished(sessionId, instrument.Meter.Name, instrument.Meter.Version, instrument.Name, instrument.Unit, Helpers.FormatTags(stats.Labels), + rateStats.Delta.HasValue ? rateStats.Delta.Value.ToString(CultureInfo.InvariantCulture) : "", rateStats.Value.ToString(CultureInfo.InvariantCulture), instrumentId); } else { - Log.UpDownCounterRateValuePublished(sessionId, instrument.Meter.Name, instrument.Meter.Version, instrument.Name, instrument.Unit, FormatTags(stats.Labels), - rateStats.Delta.HasValue ? rateStats.Delta.Value.ToString(CultureInfo.InvariantCulture) : "", - rateStats.Value.ToString(CultureInfo.InvariantCulture)); + Log.UpDownCounterRateValuePublished(sessionId, instrument.Meter.Name, instrument.Meter.Version, instrument.Name, instrument.Unit, Helpers.FormatTags(stats.Labels), + rateStats.Delta.HasValue ? rateStats.Delta.Value.ToString(CultureInfo.InvariantCulture) : "", rateStats.Value.ToString(CultureInfo.InvariantCulture), instrumentId); } } else if (stats.AggregationStatistics is LastValueStatistics lastValueStats) { - Log.GaugeValuePublished(sessionId, instrument.Meter.Name, instrument.Meter.Version, instrument.Name, instrument.Unit, FormatTags(stats.Labels), - lastValueStats.LastValue.HasValue ? lastValueStats.LastValue.Value.ToString(CultureInfo.InvariantCulture) : ""); + Log.GaugeValuePublished(sessionId, instrument.Meter.Name, instrument.Meter.Version, instrument.Name, instrument.Unit, Helpers.FormatTags(stats.Labels), + lastValueStats.LastValue.HasValue ? lastValueStats.LastValue.Value.ToString(CultureInfo.InvariantCulture) : "", instrumentId); } else if (stats.AggregationStatistics is HistogramStatistics histogramStats) { - Log.HistogramValuePublished(sessionId, instrument.Meter.Name, instrument.Meter.Version, instrument.Name, instrument.Unit, FormatTags(stats.Labels), FormatQuantiles(histogramStats.Quantiles), histogramStats.Count, histogramStats.Sum); - } - } - - private static string FormatScopeHash(object? scope) => - scope is null ? string.Empty : RuntimeHelpers.GetHashCode(scope).ToString(CultureInfo.InvariantCulture); - - private static string FormatTags(IEnumerable>? tags) - { - if (tags is null) - { - return string.Empty; - } - - StringBuilder sb = new StringBuilder(); - bool first = true; - foreach (KeyValuePair tag in tags) - { - if (first) - { - first = false; - } - else - { - sb.Append(','); - } - - sb.Append(tag.Key).Append('='); - - if (tag.Value is not null) - { - sb.Append(tag.Value.ToString()); - } - } - return sb.ToString(); - } - - private static string FormatTags(KeyValuePair[] labels) - { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < labels.Length; i++) - { - sb.Append(labels[i].Key).Append('=').Append(labels[i].Value); - if (i != labels.Length - 1) - { - sb.Append(','); - } + Log.HistogramValuePublished(sessionId, instrument.Meter.Name, instrument.Meter.Version, instrument.Name, instrument.Unit, Helpers.FormatTags(stats.Labels), FormatQuantiles(histogramStats.Quantiles), + histogramStats.Count, histogramStats.Sum, instrumentId); } - return sb.ToString(); } private static string FormatQuantiles(QuantileValue[] quantiles) diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricEventSourceTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricEventSourceTests.cs index 43aeb17afa2bc..3e2b40d51d1e5 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricEventSourceTests.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/MetricEventSourceTests.cs @@ -1516,37 +1516,68 @@ public void EventSourceEnforcesHistogramLimitAndNotMaxTimeSeries() AssertCollectStartStopEventsPresent(events, IntervalSecs, 3); } - private static string FormatScopeHash(object? scope) => - scope is null ? string.Empty : RuntimeHelpers.GetHashCode(scope).ToString(CultureInfo.InvariantCulture); + public static IEnumerable DifferentMetersAndInstrumentsData() + { + yield return new object[] { new Meter("M1").CreateCounter("C1"), new Meter("M1").CreateCounter("C1"), false}; + + var counter = new Meter("M1").CreateCounter("C1"); + yield return new object[] { counter, counter.Meter.CreateCounter("C1"), false }; + + // Same counters + counter = new Meter("M1").CreateCounter("C1"); + yield return new object[] { counter, counter, true }; + + var scope = new object(); + yield return new object[] + { + new Meter("M1", "v1", new TagList { { "k1", "v1" } }, scope).CreateCounter("C1", "u1", "d1", new TagList { { "k2", "v2" } } ), + new Meter("M1", "v1", new TagList { { "k1", "v1" } }, scope).CreateCounter("C1", "u1", "d1", new TagList { { "k2", "v2" } } ), + false, // Same Instrument + }; - private static string FormatTags(IEnumerable>? tags) + Meter meter = new Meter("M1", "v1", new TagList { { "k1", "v1" } }, scope); + yield return new object[] { meter.CreateCounter("C1", "u1", "d1", new TagList { { "k2", "v2" } } ), meter.CreateCounter("C1", "u1", "d1", new TagList { { "k2", "v2" } } ), false }; + } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [OuterLoop("Slow and has lots of console spew")] + [MemberData(nameof(DifferentMetersAndInstrumentsData))] + public void TestDifferentMetersAndInstruments(Counter counter1, Counter counter2, bool isSameCounters) { - if (tags is null) + Assert.Equal(object.ReferenceEquals(counter1, counter2), isSameCounters); + + EventWrittenEventArgs[] events; + using (MetricsEventListener listener = new MetricsEventListener(_output, MetricsEventListener.TimeSeriesValues, isShared: true, IntervalSecs, counter1.Meter.Name, counter2.Meter.Name)) { - return string.Empty; + listener.WaitForCollectionStop(s_waitForEventTimeout, 1); + counter1.Add(1); + counter2.Add(1); + listener.WaitForCollectionStop(s_waitForEventTimeout, 2); + events = listener.Events.ToArray(); } - StringBuilder sb = new StringBuilder(); - bool first = true; - foreach (KeyValuePair tag in tags) - { - if (first) - { - first = false; - } - else + var counterEvents = events.Where(e => e.EventName == "CounterRateValuePublished").Select(e => + new { - sb.Append(','); - } - - sb.Append(tag.Key).Append('='); + MeterName = e.Payload[1].ToString(), + MeterVersion = e.Payload[2].ToString(), + InstrumentName = e.Payload[3].ToString(), + Unit = e.Payload[4].ToString(), + Tags = e.Payload[5].ToString(), + Rate = e.Payload[6].ToString(), + Value = e.Payload[7].ToString(), + InstrumentId = (int)(e.Payload[8]) + }).ToArray(); - if (tag.Value is not null) - { - sb.Append(tag.Value.ToString()); - } + if (isSameCounters) + { + Assert.Equal(1, counterEvents.Length); + } + else + { + Assert.Equal(2, counterEvents.Length); + Assert.NotEqual(counterEvents[0].InstrumentId, counterEvents[1].InstrumentId); } - return sb.ToString(); } private static void AssertBeginInstrumentReportingEventsPresent(EventWrittenEventArgs[] events, params Instrument[] expectedInstruments) @@ -1562,7 +1593,8 @@ private static void AssertBeginInstrumentReportingEventsPresent(EventWrittenEven Description = e.Payload[6].ToString(), InstrumentTags = e.Payload[7].ToString(), MeterTags = e.Payload[8].ToString(), - ScopeHash = e.Payload[9].ToString() + ScopeHash = e.Payload[9].ToString(), + InstrumentId = (int)(e.Payload[10]), }).ToArray(); foreach(Instrument i in expectedInstruments) @@ -1573,9 +1605,10 @@ private static void AssertBeginInstrumentReportingEventsPresent(EventWrittenEven Assert.Equal(i.GetType().Name, e.InstrumentType); Assert.Equal(i.Unit ?? "", e.Unit); Assert.Equal(i.Description ?? "", e.Description); - Assert.Equal(FormatTags(i.Tags), e.InstrumentTags); - Assert.Equal(FormatTags(i.Meter.Tags), e.MeterTags); - Assert.Equal(FormatScopeHash(i.Meter.Scope), e.ScopeHash); + Assert.Equal(Helpers.FormatTags(i.Tags), e.InstrumentTags); + Assert.Equal(Helpers.FormatTags(i.Meter.Tags), e.MeterTags); + Assert.Equal(Helpers.FormatObjectHash(i.Meter.Scope), e.ScopeHash); + Assert.True(e.InstrumentId > 0); } Assert.Equal(expectedInstruments.Length, beginReportEvents.Length); @@ -1594,7 +1627,8 @@ private static void AssertEndInstrumentReportingEventsPresent(EventWrittenEventA Description = e.Payload[6].ToString(), InstrumentTags = e.Payload[7].ToString(), MeterTags = e.Payload[8].ToString(), - ScopeHash = e.Payload[9].ToString() + ScopeHash = e.Payload[9].ToString(), + InstrumentId = (int)(e.Payload[10]), }).ToArray(); foreach (Instrument i in expectedInstruments) @@ -1605,9 +1639,10 @@ private static void AssertEndInstrumentReportingEventsPresent(EventWrittenEventA Assert.Equal(i.GetType().Name, e.InstrumentType); Assert.Equal(i.Unit ?? "", e.Unit); Assert.Equal(i.Description ?? "", e.Description); - Assert.Equal(FormatTags(i.Tags), e.InstrumentTags); - Assert.Equal(FormatTags(i.Meter.Tags), e.MeterTags); - Assert.Equal(FormatScopeHash(i.Meter.Scope), e.ScopeHash); + Assert.Equal(Helpers.FormatTags(i.Tags), e.InstrumentTags); + Assert.Equal(Helpers.FormatTags(i.Meter.Tags), e.MeterTags); + Assert.Equal(Helpers.FormatObjectHash(i.Meter.Scope), e.ScopeHash); + Assert.True(e.InstrumentId > 0); } Assert.Equal(expectedInstruments.Length, beginReportEvents.Length); @@ -1646,7 +1681,8 @@ private static void AssertInstrumentPublishingEventsPresent(EventWrittenEventArg Description = e.Payload[6].ToString(), InstrumentTags = e.Payload[7].ToString(), MeterTags = e.Payload[8].ToString(), - ScopeHash = e.Payload[9].ToString() + ScopeHash = e.Payload[9].ToString(), + InstrumentId = (int)(e.Payload[10]), }).ToArray(); foreach (Instrument i in expectedInstruments) @@ -1657,9 +1693,10 @@ private static void AssertInstrumentPublishingEventsPresent(EventWrittenEventArg Assert.Equal(i.GetType().Name, e.InstrumentType); Assert.Equal(i.Unit ?? "", e.Unit); Assert.Equal(i.Description ?? "", e.Description); - Assert.Equal(FormatTags(i.Tags), e.InstrumentTags); - Assert.Equal(FormatTags(i.Meter.Tags), e.MeterTags); - Assert.Equal(FormatScopeHash(i.Meter.Scope), e.ScopeHash); + Assert.Equal(Helpers.FormatTags(i.Tags), e.InstrumentTags); + Assert.Equal(Helpers.FormatTags(i.Meter.Tags), e.MeterTags); + Assert.Equal(Helpers.FormatObjectHash(i.Meter.Scope), e.ScopeHash); + Assert.True(e.InstrumentId > 0); } Assert.Equal(expectedInstruments.Length, publishEvents.Length); @@ -1689,15 +1726,18 @@ private static void AssertGenericCounterEventsPresent(string eventName, EventWri Unit = e.Payload[4].ToString(), Tags = e.Payload[5].ToString(), Rate = e.Payload[6].ToString(), - Value = e.Payload[7].ToString() + Value = e.Payload[7].ToString(), + InstrumentId = (int)(e.Payload[7]), }).ToArray(); var filteredEvents = counterEvents.Where(e => e.MeterName == meterName && e.InstrumentName == instrumentName && e.Tags == tags).ToArray(); Assert.True(filteredEvents.Length >= expected.Length); + for (int i = 0; i < expected.Length; i++) { Assert.Equal(expectedUnit, filteredEvents[i].Unit); Assert.Equal(expected[i].Item1, filteredEvents[i].Rate); Assert.Equal(expected[i].Item2, filteredEvents[i].Value); + Assert.True(filteredEvents[i].InstrumentId > 0); } } @@ -1727,13 +1767,16 @@ private static void AssertGaugeEventsPresent(EventWrittenEventArgs[] events, str Unit = e.Payload[4].ToString(), Tags = e.Payload[5].ToString(), Value = e.Payload[6].ToString(), + InstrumentId = (int)(e.Payload[7]), }).ToArray(); var filteredEvents = counterEvents.Where(e => e.MeterName == meterName && e.InstrumentName == instrumentName && e.Tags == tags).ToArray(); Assert.True(filteredEvents.Length >= expectedValues.Length); + for (int i = 0; i < expectedValues.Length; i++) { Assert.Equal(expectedUnit, filteredEvents[i].Unit); Assert.Equal(expectedValues[i], filteredEvents[i].Value); + Assert.True(filteredEvents[i].InstrumentId > 0); } } @@ -1750,16 +1793,19 @@ private static void AssertHistogramEventsPresent(EventWrittenEventArgs[] events, Tags = e.Payload[5].ToString(), Quantiles = (string)e.Payload[6], Count = e.Payload[7].ToString(), - Sum = e.Payload[8].ToString() + Sum = e.Payload[8].ToString(), + InstrumentId = (int)(e.Payload[9]) }).ToArray(); var filteredEvents = counterEvents.Where(e => e.MeterName == meterName && e.InstrumentName == instrumentName && e.Tags == tags).ToArray(); Assert.True(filteredEvents.Length >= expected.Length); + for (int i = 0; i < expected.Length; i++) { Assert.Equal(filteredEvents[i].Unit, expectedUnit); Assert.Equal(expected[i].Item1, filteredEvents[i].Quantiles); Assert.Equal(expected[i].Item2, filteredEvents[i].Count); Assert.Equal(expected[i].Item3, filteredEvents[i].Sum); + Assert.True(filteredEvents[i].InstrumentId > 0); } } diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.csproj b/src/libraries/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.csproj index 5adb835cb70e2..4b9d949f96b4f 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.csproj +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/System.Diagnostics.DiagnosticSource.Tests.csproj @@ -15,6 +15,7 @@ +