Skip to content

Commit

Permalink
System.Diagnostics.Activity: Implement Enumerate* API (#67920)
Browse files Browse the repository at this point in the history
  • Loading branch information
CodeBlanch authored Apr 13, 2022
1 parent 16b7255 commit 7bac4e8
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<System.Collections.Generic.KeyValuePair<string, object?>> EnumerateTagObjects() { throw null; }
public System.Diagnostics.Activity.Enumerator<ActivityEvent> EnumerateEvents() { throw null; }
public System.Diagnostics.Activity.Enumerator<ActivityLink> EnumerateLinks() { throw null; }

public struct Enumerator<T>
{
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public readonly System.Diagnostics.Activity.Enumerator<T> GetEnumerator() { throw null; }
public readonly ref T Current { get { throw null; } }
public bool MoveNext() { throw null; }
}
}
public readonly struct ActivityChangedEventArgs
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,24 @@ public IEnumerable<ActivityLink> Links
}
}

/// <summary>
/// Enumerate the tags attached to this Activity object.
/// </summary>
/// <returns><see cref="Enumerator{T}"/>.</returns>
public Enumerator<KeyValuePair<string, object?>> EnumerateTagObjects() => new Enumerator<KeyValuePair<string, object?>>(_tags?.First);

/// <summary>
/// Enumerate the <see cref="ActivityEvent" /> objects attached to this Activity object.
/// </summary>
/// <returns><see cref="Enumerator{T}"/>.</returns>
public Enumerator<ActivityEvent> EnumerateEvents() => new Enumerator<ActivityEvent>(_events?.First);

/// <summary>
/// Enumerate the <see cref="ActivityLink" /> objects attached to this Activity object.
/// </summary>
/// <returns><see cref="Enumerator{T}"/>.</returns>
public Enumerator<ActivityLink> EnumerateLinks() => new Enumerator<ActivityLink>(_links?.First);

/// <summary>
/// Returns the value of the key-value pair added to the activity with <see cref="AddBaggage(string, string)"/>.
/// Returns null if that key does not exist.
Expand Down Expand Up @@ -1370,6 +1388,55 @@ public ActivityIdFormat IdFormat
private set => _state = (_state & ~State.FormatFlags) | (State)((byte)value & (byte)State.FormatFlags);
}

/// <summary>
/// Enumerates the data stored on an Activity object.
/// </summary>
/// <typeparam name="T">Type being enumerated.</typeparam>
public struct Enumerator<T>
{
private static readonly DiagNode<T> s_Empty = new DiagNode<T>(default!);

private DiagNode<T>? _nextNode;
private DiagNode<T> _currentNode;

internal Enumerator(DiagNode<T>? head)
{
_nextNode = head;
_currentNode = s_Empty;
}

/// <summary>
/// Returns an enumerator that iterates through the data stored on an Activity object.
/// </summary>
/// <returns><see cref="Enumerator{T}"/>.</returns>
[ComponentModel.EditorBrowsable(ComponentModel.EditorBrowsableState.Never)] // Only here to make foreach work
public readonly Enumerator<T> GetEnumerator() => this;

/// <summary>
/// Gets the element at the current position of the enumerator.
/// </summary>
public readonly ref T Current => ref _currentNode.Value;

/// <summary>
/// Advances the enumerator to the next element of the data.
/// </summary>
/// <returns><see langword="true"/> if the enumerator was successfully advanced to the
/// next element; <see langword="false"/> if the enumerator has passed the end of the
/// collection.</returns>
public bool MoveNext()
{
if (_nextNode == null)
{
_currentNode = s_Empty;
return false;
}

_currentNode = _nextNode;
_nextNode = _nextNode.Next;
return true;
}
}

private sealed class BaggageLinkedList : IEnumerable<KeyValuePair<string, string?>>
{
private DiagNode<KeyValuePair<string, string?>>? _first;
Expand Down Expand Up @@ -1446,8 +1513,7 @@ public void Remove(string key)
}
}

// Note: Some consumers use this GetEnumerator dynamically to avoid allocations.
public Enumerator<KeyValuePair<string, string?>> GetEnumerator() => new Enumerator<KeyValuePair<string, string?>>(_first);
public DiagEnumerator<KeyValuePair<string, string?>> GetEnumerator() => new DiagEnumerator<KeyValuePair<string, string?>>(_first);
IEnumerator<KeyValuePair<string, string?>> IEnumerable<KeyValuePair<string, string?>>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
Expand All @@ -1472,6 +1538,8 @@ public TagsLinkedList(IEnumerator<KeyValuePair<string, object?>> e)
}
}

public DiagNode<KeyValuePair<string, object?>>? First => _first;

public TagsLinkedList(IEnumerable<KeyValuePair<string, object?>> 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.
Expand Down Expand Up @@ -1608,8 +1676,7 @@ public void Set(KeyValuePair<string, object?> value)
}
}

// Note: Some consumers use this GetEnumerator dynamically to avoid allocations.
public Enumerator<KeyValuePair<string, object?>> GetEnumerator() => new Enumerator<KeyValuePair<string, object?>>(_first);
public DiagEnumerator<KeyValuePair<string, object?>> GetEnumerator() => new DiagEnumerator<KeyValuePair<string, object?>>(_first);
IEnumerator<KeyValuePair<string, object?>> IEnumerable<KeyValuePair<string, object?>>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

namespace System.Diagnostics
{
Expand Down Expand Up @@ -147,21 +146,19 @@ public void AddFront(T value)
}
}

// Note: Some consumers use this GetEnumerator dynamically to avoid allocations.
public Enumerator<T> GetEnumerator() => new Enumerator<T>(_first);
public DiagEnumerator<T> GetEnumerator() => new DiagEnumerator<T>(_first);
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

// Note: Some consumers use this Enumerator dynamically to avoid allocations.
internal struct Enumerator<T> : IEnumerator<T>
internal struct DiagEnumerator<T> : IEnumerator<T>
{
private static readonly DiagNode<T> s_Empty = new DiagNode<T>(default!);

private DiagNode<T>? _nextNode;
private DiagNode<T> _currentNode;

public Enumerator(DiagNode<T>? head)
public DiagEnumerator(DiagNode<T>? head)
{
_nextNode = head;
_currentNode = s_Empty;
Expand Down Expand Up @@ -190,5 +187,4 @@ public void Dispose()
{
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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 = () =>
{
Expand All @@ -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<KeyValuePair<string, object>> values = new();

Assert.True(enumerator.MoveNext());
Assert.Equal(new KeyValuePair<string, object?>("key1", "value1"), enumerator.Current);
values.Add(enumerator.Current);
Assert.True(enumerator.MoveNext());
Assert.Equal(new KeyValuePair<string, object?>("key2", "value2"), enumerator.Current);
values.Add(enumerator.Current);
Assert.False(enumerator.MoveNext());

Assert.Equal(a.TagObjects, values);

foreach (ref readonly KeyValuePair<string, object?> 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<ActivityEvent> 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<ActivityContext> 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<ActivityLink> 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;
Expand Down

0 comments on commit 7bac4e8

Please sign in to comment.