Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Activity.EnumerateLinks extension #1314

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 50 additions & 5 deletions src/OpenTelemetry.Api/Trace/ActivityExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ namespace OpenTelemetry.Trace
public static class ActivityExtensions
{
private static readonly object EmptyActivityTagObjects = typeof(Activity).GetField("s_emptyTagObjects", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);
private static readonly object EmptyActivityLinks = typeof(Activity).GetField("s_emptyLinks", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);

/// <summary>
/// Sets the status of activity execution.
Expand Down Expand Up @@ -99,18 +100,33 @@ public static object GetTagValue(this Activity activity, string tagName)
/// <summary>
/// Enumerates all the key/value pairs on an <see cref="Activity"/> without performing an allocation.
/// </summary>
/// <typeparam name="T">The struct <see cref="IActivityTagEnumerator"/> implementation to use for the enumeration.</typeparam>
/// <typeparam name="T">The struct <see cref="IActivityEnumerator{T}"/> implementation to use for the enumeration.</typeparam>
/// <param name="activity">Activity instance.</param>
/// <param name="tagEnumerator">Tag enumerator.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void EnumerateTagValues<T>(this Activity activity, ref T tagEnumerator)
where T : struct, IActivityTagEnumerator
where T : struct, IActivityEnumerator<KeyValuePair<string, object>>
{
Debug.Assert(activity != null, "Activity should not be null");

ActivityTagObjectsEnumeratorFactory<T>.Enumerate(activity, ref tagEnumerator);
}

/// <summary>
/// Enumerates all the <see cref="ActivityLink"/>s on an <see cref="Activity"/> without performing an allocation.
/// </summary>
/// <typeparam name="T">The struct <see cref="IActivityEnumerator{T}"/> implementation to use for the enumeration.</typeparam>
/// <param name="activity">Activity instance.</param>
/// <param name="linkEnumerator">Tag enumerator.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void EnumerateLinks<T>(this Activity activity, ref T linkEnumerator)
where T : struct, IActivityEnumerator<ActivityLink>
{
Debug.Assert(activity != null, "Activity should not be null");

ActivityLinksEnumeratorFactory<T>.Enumerate(activity, ref linkEnumerator);
}

/// <summary>
/// Record Exception.
/// </summary>
Expand Down Expand Up @@ -138,7 +154,7 @@ public static void RecordException(this Activity activity, Exception ex)
activity?.AddEvent(new ActivityEvent(SemanticConventions.AttributeExceptionEventName, default, tagsCollection));
}

private struct ActivitySingleTagEnumerator : IActivityTagEnumerator
private struct ActivitySingleTagEnumerator : IActivityEnumerator<KeyValuePair<string, object>>
{
private readonly string tagName;

Expand All @@ -162,7 +178,7 @@ public bool ForEach(KeyValuePair<string, object> item)
}
}

private struct ActivityStatusTagEnumerator : IActivityTagEnumerator
private struct ActivityStatusTagEnumerator : IActivityEnumerator<KeyValuePair<string, object>>
{
public string StatusCode { get; private set; }

Expand All @@ -185,7 +201,7 @@ public bool ForEach(KeyValuePair<string, object> item)
}

private static class ActivityTagObjectsEnumeratorFactory<TState>
where TState : struct, IActivityTagEnumerator
where TState : struct, IActivityEnumerator<KeyValuePair<string, object>>
{
private static readonly DictionaryEnumerator<string, object, TState>.AllocationFreeForEachDelegate
ActivityTagObjectsEnumerator = DictionaryEnumerator<string, object, TState>.BuildAllocationFreeForEachDelegate(
Expand All @@ -212,5 +228,34 @@ public static void Enumerate(Activity activity, ref TState state)
private static bool ForEachTagValueCallback(ref TState state, KeyValuePair<string, object> item)
=> state.ForEach(item);
}

private static class ActivityLinksEnumeratorFactory<TState>
where TState : struct, IActivityEnumerator<ActivityLink>
{
private static readonly ListEnumerator<ActivityLink, TState>.AllocationFreeForEachDelegate
ActivityLinksEnumerator = ListEnumerator<ActivityLink, TState>.BuildAllocationFreeForEachDelegate(
typeof(Activity).GetField("_links", BindingFlags.Instance | BindingFlags.NonPublic).FieldType);

private static readonly ListEnumerator<ActivityLink, TState>.ForEachDelegate ForEachLinkCallbackRef = ForEachLinkCallback;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Enumerate(Activity activity, ref TState state)
{
var activityLinks = activity.Links;

if (ReferenceEquals(activityLinks, EmptyActivityLinks))
{
return;
}

ActivityLinksEnumerator(
activityLinks,
ref state,
ForEachLinkCallbackRef);
}

private static bool ForEachLinkCallback(ref TState state, ActivityLink item)
=> state.ForEach(item);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// <copyright file="IActivityTagEnumerator.cs" company="OpenTelemetry Authors">
// <copyright file="IActivityEnumerator.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
Expand All @@ -14,21 +14,21 @@
// limitations under the License.
// </copyright>

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

namespace OpenTelemetry.Trace
{
/// <summary>
/// An interface used to perform zero-allocation enumeration of <see cref="Activity"/> tags. Implementation must be a struct.
/// An interface used to perform zero-allocation enumeration of <see cref="Activity"/> elements. Implementation must be a struct.
/// </summary>
public interface IActivityTagEnumerator
/// <typeparam name="T">Enumerated item type.</typeparam>
public interface IActivityEnumerator<T>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I switched IActivityTagEnumerator to IActivityEnumerator<T> so I could use it for tags, links, and events (up next). Breaking change, which is a con, but pro is with this change we don't need a bunch of interfaces that are basically the same, just different item types.

{
/// <summary>
/// Called for each <see cref="Activity"/> tag while the enumeration is executing.
/// Called for each <see cref="Activity"/> item while the enumeration is executing.
/// </summary>
/// <param name="item">Tag key/value pair.</param>
/// <param name="item">Enumeration item.</param>
/// <returns><see langword="true"/> to continue the enumeration of records or <see langword="false"/> to stop (break) the enumeration.</returns>
bool ForEach(KeyValuePair<string, object> item);
bool ForEach(T item);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ internal static class JaegerActivityExtensions
[SemanticConventions.AttributeDbInstance] = 2, // peer.service for Redis.
};

private static readonly ListEnumerator<ActivityLink, PooledListState<JaegerSpanRef>>.ForEachDelegate ProcessActivityLinkRef = ProcessActivityLink;
private static readonly ListEnumerator<ActivityEvent, PooledListState<JaegerLog>>.ForEachDelegate ProcessActivityEventRef = ProcessActivityEvent;
private static readonly DictionaryEnumerator<string, object, PooledListState<JaegerTag>>.ForEachDelegate ProcessTagRef = ProcessTag;

Expand Down Expand Up @@ -155,24 +154,16 @@ public static JaegerSpan ToJaegerSpan(this Activity activity)
flags: (activity.Context.TraceFlags & ActivityTraceFlags.Recorded) > 0 ? 0x1 : 0,
startTime: ToEpochMicroseconds(activity.StartTimeUtc),
duration: (long)activity.Duration.TotalMilliseconds * 1000,
references: activity.Links.ToJaegerSpanRefs(),
references: activity.ToJaegerSpanRefs(),
tags: jaegerTags.Tags,
logs: activity.Events.ToJaegerLogs());
}

public static PooledList<JaegerSpanRef> ToJaegerSpanRefs(this IEnumerable<ActivityLink> links)
public static PooledList<JaegerSpanRef> ToJaegerSpanRefs(this Activity activity)
{
PooledListState<JaegerSpanRef> references = default;
LinkState references = default;

if (links == null)
{
return references.List;
}

ListEnumerator<ActivityLink, PooledListState<JaegerSpanRef>>.AllocationFreeForEach(
links,
ref references,
ProcessActivityLinkRef);
activity.EnumerateLinks(ref references);

return references.List;
}
Expand Down Expand Up @@ -324,19 +315,6 @@ private static void ProcessJaegerTag(ref TagState state, string key, JaegerTag j
PooledList<JaegerTag>.Add(ref state.Tags, jaegerTag);
}

private static bool ProcessActivityLink(ref PooledListState<JaegerSpanRef> state, ActivityLink link)
{
if (!state.Created)
{
state.List = PooledList<JaegerSpanRef>.Create();
state.Created = true;
}

PooledList<JaegerSpanRef>.Add(ref state.List, link.ToJaegerSpanRef());

return true;
}

private static bool ProcessActivityEvent(ref PooledListState<JaegerLog> state, ActivityEvent e)
{
if (!state.Created)
Expand All @@ -363,7 +341,7 @@ private static bool ProcessTag(ref PooledListState<JaegerTag> state, KeyValuePai
return true;
}

private struct TagState : IActivityTagEnumerator
private struct TagState : IActivityEnumerator<KeyValuePair<string, object>>
{
public PooledList<JaegerTag> Tags;

Expand Down Expand Up @@ -392,6 +370,26 @@ public bool ForEach(KeyValuePair<string, object> activityTag)
}
}

private struct LinkState : IActivityEnumerator<ActivityLink>
{
public bool Created;

public PooledList<JaegerSpanRef> List;

public bool ForEach(ActivityLink activityLink)
{
if (!this.Created)
{
this.List = PooledList<JaegerSpanRef>.Create();
this.Created = true;
}

PooledList<JaegerSpanRef>.Add(ref this.List, activityLink.ToJaegerSpanRef());

return true;
}
}

private struct PooledListState<T>
{
public bool Created;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ private static bool ProcessActivityEvents(ref PooledList<ZipkinAnnotation> annot
return true;
}

internal struct AttributeEnumerationState : IActivityTagEnumerator
internal struct AttributeEnumerationState : IActivityEnumerator<KeyValuePair<string, object>>
{
public PooledList<KeyValuePair<string, object>> Tags;

Expand Down
59 changes: 56 additions & 3 deletions test/Benchmarks/ActivityBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using BenchmarkDotNet.Attributes;
using OpenTelemetry.Trace;

Expand All @@ -30,9 +31,30 @@ public class ActivityBenchmarks

static ActivityBenchmarks()
{
using ActivitySource activitySource = new ActivitySource("Benchmarks");

ActivitySource.AddActivityListener(
new ActivityListener
{
ShouldListenTo = (source) => true,
Sample = (ref ActivityCreationOptions<ActivityContext> options) => ActivitySamplingResult.AllData,
});

EmptyActivity = new Activity("EmptyActivity");

Activity = new Activity("Activity");
var activityLinks = new[]
{
new ActivityLink(default),
new ActivityLink(default),
new ActivityLink(default),
};

Activity = activitySource.StartActivity(
"Activity",
ActivityKind.Internal,
parentContext: default,
links: activityLinks);

Activity.SetTag("Tag1", "Value1");
Activity.SetTag("Tag2", 2);
Activity.SetTag("Tag3", false);
Expand All @@ -41,6 +63,8 @@ static ActivityBenchmarks()
{
Activity.AddTag($"AutoTag{i}", i);
}

Activity.Stop();
}

[Benchmark]
Expand Down Expand Up @@ -108,12 +132,30 @@ public void EnumerateNonemptyTagObjects()
[Benchmark]
public void EnumerateTagValuesNonemptyTagObjects()
{
Enumerator state = default;
TagEnumerator state = default;

Activity.EnumerateTagValues(ref state);
}

private struct Enumerator : IActivityTagEnumerator
[Benchmark]
public void EnumerateNonemptyActivityLinks()
{
int count = 0;
foreach (ActivityLink activityLink in Activity.Links)
{
count++;
}
}

[Benchmark]
public void EnumerateLinksNonemptyActivityLinks()
{
LinkEnumerator state = default;

Activity.EnumerateLinks(ref state);
}

private struct TagEnumerator : IActivityEnumerator<KeyValuePair<string, object>>
{
public int Count { get; private set; }

Expand All @@ -123,5 +165,16 @@ public bool ForEach(KeyValuePair<string, object> item)
return true;
}
}

private struct LinkEnumerator : IActivityEnumerator<ActivityLink>
{
public int Count { get; private set; }

public bool ForEach(ActivityLink item)
{
this.Count++;
return true;
}
}
}
}