From 7bac4e8fa611eb02ac1bd27b97a8570cbf321437 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Wed, 13 Apr 2022 15:02:29 -0700 Subject: [PATCH] System.Diagnostics.Activity: Implement Enumerate* API (#67920) --- ...em.Diagnostics.DiagnosticSourceActivity.cs | 13 +- .../src/System/Diagnostics/Activity.cs | 75 +++++++++- .../src/System/Diagnostics/DiagLinkedList.cs | 10 +- .../tests/ActivityTests.cs | 139 +++++++++++++++++- 4 files changed, 217 insertions(+), 20 deletions(-) diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs index c82e98eed32a0..637d9f456b22c 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs @@ -69,7 +69,18 @@ public string? Id protected virtual void Dispose(bool disposing) { throw null; } public void SetCustomProperty(string propertyName, object? propertyValue) { throw null; } public object? GetCustomProperty(string propertyName) { throw null; } - public ActivityContext Context { get { throw null; } } + public System.Diagnostics.ActivityContext Context { get { throw null; } } + public System.Diagnostics.Activity.Enumerator> EnumerateTagObjects() { throw null; } + public System.Diagnostics.Activity.Enumerator EnumerateEvents() { throw null; } + public System.Diagnostics.Activity.Enumerator EnumerateLinks() { throw null; } + + public struct Enumerator + { + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public readonly System.Diagnostics.Activity.Enumerator GetEnumerator() { throw null; } + public readonly ref T Current { get { throw null; } } + public bool MoveNext() { throw null; } + } } public readonly struct ActivityChangedEventArgs { diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs index b7b0ea61dcf5e..808f1ea251082 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs @@ -389,6 +389,24 @@ public IEnumerable Links } } + /// + /// Enumerate the tags attached to this Activity object. + /// + /// . + public Enumerator> EnumerateTagObjects() => new Enumerator>(_tags?.First); + + /// + /// Enumerate the objects attached to this Activity object. + /// + /// . + public Enumerator EnumerateEvents() => new Enumerator(_events?.First); + + /// + /// Enumerate the objects attached to this Activity object. + /// + /// . + public Enumerator EnumerateLinks() => new Enumerator(_links?.First); + /// /// Returns the value of the key-value pair added to the activity with . /// Returns null if that key does not exist. @@ -1370,6 +1388,55 @@ public ActivityIdFormat IdFormat private set => _state = (_state & ~State.FormatFlags) | (State)((byte)value & (byte)State.FormatFlags); } + /// + /// Enumerates the data stored on an Activity object. + /// + /// Type being enumerated. + public struct Enumerator + { + private static readonly DiagNode s_Empty = new DiagNode(default!); + + private DiagNode? _nextNode; + private DiagNode _currentNode; + + internal Enumerator(DiagNode? head) + { + _nextNode = head; + _currentNode = s_Empty; + } + + /// + /// Returns an enumerator that iterates through the data stored on an Activity object. + /// + /// . + [ComponentModel.EditorBrowsable(ComponentModel.EditorBrowsableState.Never)] // Only here to make foreach work + public readonly Enumerator GetEnumerator() => this; + + /// + /// Gets the element at the current position of the enumerator. + /// + public readonly ref T Current => ref _currentNode.Value; + + /// + /// Advances the enumerator to the next element of the data. + /// + /// if the enumerator was successfully advanced to the + /// next element; if the enumerator has passed the end of the + /// collection. + public bool MoveNext() + { + if (_nextNode == null) + { + _currentNode = s_Empty; + return false; + } + + _currentNode = _nextNode; + _nextNode = _nextNode.Next; + return true; + } + } + private sealed class BaggageLinkedList : IEnumerable> { private DiagNode>? _first; @@ -1446,8 +1513,7 @@ public void Remove(string key) } } - // Note: Some consumers use this GetEnumerator dynamically to avoid allocations. - public Enumerator> GetEnumerator() => new Enumerator>(_first); + public DiagEnumerator> GetEnumerator() => new DiagEnumerator>(_first); IEnumerator> IEnumerable>.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } @@ -1472,6 +1538,8 @@ public TagsLinkedList(IEnumerator> e) } } + public DiagNode>? First => _first; + public TagsLinkedList(IEnumerable> list) => Add(list); // Add doesn't take the lock because it is called from the Activity creation before sharing the activity object to the caller. @@ -1608,8 +1676,7 @@ public void Set(KeyValuePair value) } } - // Note: Some consumers use this GetEnumerator dynamically to avoid allocations. - public Enumerator> GetEnumerator() => new Enumerator>(_first); + public DiagEnumerator> GetEnumerator() => new DiagEnumerator>(_first); IEnumerator> IEnumerable>.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagLinkedList.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagLinkedList.cs index 8ec5e93b624bf..d5472aa6f3403 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagLinkedList.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagLinkedList.cs @@ -3,7 +3,6 @@ using System.Collections; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; namespace System.Diagnostics { @@ -147,21 +146,19 @@ public void AddFront(T value) } } - // Note: Some consumers use this GetEnumerator dynamically to avoid allocations. - public Enumerator GetEnumerator() => new Enumerator(_first); + public DiagEnumerator GetEnumerator() => new DiagEnumerator(_first); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } - // Note: Some consumers use this Enumerator dynamically to avoid allocations. - internal struct Enumerator : IEnumerator + internal struct DiagEnumerator : IEnumerator { private static readonly DiagNode s_Empty = new DiagNode(default!); private DiagNode? _nextNode; private DiagNode _currentNode; - public Enumerator(DiagNode? head) + public DiagEnumerator(DiagNode? head) { _nextNode = head; _currentNode = s_Empty; @@ -190,5 +187,4 @@ public void Dispose() { } } - } diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivityTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivityTests.cs index af33bcb6baa99..ba7559d02f7cc 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivityTests.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivityTests.cs @@ -172,7 +172,8 @@ public void TestSetBaggage() [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] public void TestBaggageWithChainedActivities() { - RemoteExecutor.Invoke(() => { + RemoteExecutor.Invoke(() => + { Activity a1 = new Activity("a1"); a1.Start(); @@ -763,7 +764,7 @@ public void IdFormat_WithTheEnvironmentSwitch() { Activity activity = new Activity("activity15"); activity.Start(); - Assert.Equal(ActivityIdFormat.Hierarchical, activity.IdFormat); + Assert.Equal(ActivityIdFormat.Hierarchical, activity.IdFormat); }, new RemoteInvokeOptions() { StartInfo = psi }).Dispose(); } @@ -1611,7 +1612,7 @@ public void TestTagObjects() Assert.Equal(tags[i].Value, tagObjects[i].Value); } - activity.AddTag("s4", (object) null); + activity.AddTag("s4", (object)null); Assert.Equal(4, activity.Tags.Count()); Assert.Equal(4, activity.TagObjects.Count()); tags = activity.Tags.ToArray(); @@ -1732,9 +1733,9 @@ public void TestGetTagItem() [Theory] - [InlineData("key1", null, true, 1)] + [InlineData("key1", null, true, 1)] [InlineData("key2", null, false, 0)] - [InlineData("key3", "v1", true, 1)] + [InlineData("key3", "v1", true, 1)] [InlineData("key4", "v2", false, 1)] public void TestInsertingFirstTag(string key, object value, bool add, int resultCount) { @@ -1829,8 +1830,8 @@ public void TestStatus() Assert.Equal(ActivityStatusCode.Error, a.Status); Assert.Equal("Another Error Code Description", a.StatusDescription); - a.SetStatus((ActivityStatusCode) 100, "Another Error Code Description"); - Assert.Equal((ActivityStatusCode) 100, a.Status); + a.SetStatus((ActivityStatusCode)100, "Another Error Code Description"); + Assert.Equal((ActivityStatusCode)100, a.Status); Assert.Null(a.StatusDescription); } @@ -2032,7 +2033,7 @@ public void TraceIdCustomGenerationTest() RemoteExecutor.Invoke(() => { Random random = new Random(); - byte [] traceIdBytes = new byte[16]; + byte[] traceIdBytes = new byte[16]; Activity.TraceIdGenerator = () => { @@ -2054,6 +2055,128 @@ public void TraceIdCustomGenerationTest() }).Dispose(); } + [Fact] + public void EnumerateTagObjectsTest() + { + Activity a = new Activity("Root"); + + var enumerator = a.EnumerateTagObjects(); + + Assert.False(enumerator.MoveNext()); + Assert.False(enumerator.GetEnumerator().MoveNext()); + + a.SetTag("key1", "value1"); + a.SetTag("key2", "value2"); + + enumerator = a.EnumerateTagObjects(); + + List> values = new(); + + Assert.True(enumerator.MoveNext()); + Assert.Equal(new KeyValuePair("key1", "value1"), enumerator.Current); + values.Add(enumerator.Current); + Assert.True(enumerator.MoveNext()); + Assert.Equal(new KeyValuePair("key2", "value2"), enumerator.Current); + values.Add(enumerator.Current); + Assert.False(enumerator.MoveNext()); + + Assert.Equal(a.TagObjects, values); + + foreach (ref readonly KeyValuePair tag in a.EnumerateTagObjects()) + { + Assert.Equal(values[0], tag); + values.RemoveAt(0); + } + } + + [Fact] + public void EnumerateEventsTest() + { + Activity a = new Activity("Root"); + + var enumerator = a.EnumerateEvents(); + + Assert.False(enumerator.MoveNext()); + Assert.False(enumerator.GetEnumerator().MoveNext()); + + a.AddEvent(new ActivityEvent("event1")); + a.AddEvent(new ActivityEvent("event2")); + + enumerator = a.EnumerateEvents(); + + List values = new(); + + Assert.True(enumerator.MoveNext()); + Assert.Equal("event1", enumerator.Current.Name); + values.Add(enumerator.Current); + Assert.True(enumerator.MoveNext()); + Assert.Equal("event2", enumerator.Current.Name); + values.Add(enumerator.Current); + Assert.False(enumerator.MoveNext()); + + Assert.Equal(a.Events, values); + + foreach (ref readonly ActivityEvent activityEvent in a.EnumerateEvents()) + { + Assert.Equal(values[0], activityEvent); + values.RemoveAt(0); + } + } + + [Fact] + public void EnumerateLinksTest() + { + Activity? a = new Activity("Root"); + + Assert.NotNull(a); + + var enumerator = a.EnumerateLinks(); + + Assert.False(enumerator.MoveNext()); + Assert.False(enumerator.GetEnumerator().MoveNext()); + + using ActivitySource source = new ActivitySource("test"); + + using ActivityListener listener = new ActivityListener() + { + ShouldListenTo = (source) => true, + Sample = (ref ActivityCreationOptions options) => ActivitySamplingResult.AllDataAndRecorded + }; + + ActivitySource.AddActivityListener(listener); + + var context1 = new ActivityContext(ActivityTraceId.CreateRandom(), default, ActivityTraceFlags.None); + var context2 = new ActivityContext(ActivityTraceId.CreateRandom(), default, ActivityTraceFlags.None); + + a = source.CreateActivity( + name: "Root", + kind: ActivityKind.Internal, + parentContext: default, + links: new[] { new ActivityLink(context1), new ActivityLink(context2) }); + + Assert.NotNull(a); + + enumerator = a.EnumerateLinks(); + + List values = new(); + + Assert.True(enumerator.MoveNext()); + Assert.Equal(context1.TraceId, enumerator.Current.Context.TraceId); + values.Add(enumerator.Current); + Assert.True(enumerator.MoveNext()); + Assert.Equal(context2.TraceId, enumerator.Current.Context.TraceId); + values.Add(enumerator.Current); + Assert.False(enumerator.MoveNext()); + + Assert.Equal(a.Links, values); + + foreach (ref readonly ActivityLink activityLink in a.EnumerateLinks()) + { + Assert.Equal(values[0], activityLink); + values.RemoveAt(0); + } + } + public void Dispose() { Activity.Current = null;