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 ActivityEvent.EnumerateTags & ActivityLink.EnumerateTags extensions #1320

Merged
Merged
Show file tree
Hide file tree
Changes from 5 commits
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
4 changes: 4 additions & 0 deletions src/OpenTelemetry.Api/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
* Added `EnumerateEvents` extension method on `Activity` for retrieving events
efficiently
([#1319](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1319))
* Added `EnumerateTags` extension methods on `ActivityLink` & `ActivityEvent`
for retrieving tags efficiently. Renamed `Activity.EnumerateTagValues` ->
`Activity.EnumerateTags`.
([#1320](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1320))

## 0.6.0-beta.1

Expand Down
61 changes: 60 additions & 1 deletion src/OpenTelemetry.Api/Trace/ActivityExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public static object GetTagValue(this Activity activity, string tagName)
/// <param name="tagEnumerator">Tag enumerator.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "ActivityProcessor is hot path")]
public static void EnumerateTagValues<T>(this Activity activity, ref T tagEnumerator)
public static void EnumerateTags<T>(this Activity activity, ref T tagEnumerator)
where T : struct, IActivityEnumerator<KeyValuePair<string, object>>
{
Debug.Assert(activity != null, "Activity should not be null");
Expand All @@ -127,6 +127,20 @@ public static void EnumerateLinks<T>(this Activity activity, ref T linkEnumerato
ActivityLinksEnumeratorFactory<T>.Enumerate(activity, ref linkEnumerator);
}

/// <summary>
/// Enumerates all the key/value pairs on an <see cref="ActivityLink"/> without performing an allocation.
/// </summary>
/// <typeparam name="T">The struct <see cref="IActivityEnumerator{T}"/> implementation to use for the enumeration.</typeparam>
/// <param name="activityLink">ActivityLink instance.</param>
/// <param name="tagEnumerator">Tag enumerator.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "ActivityProcessor is hot path")]
public static void EnumerateTags<T>(this ActivityLink activityLink, ref T tagEnumerator)
where T : struct, IActivityEnumerator<KeyValuePair<string, object>>
{
ActivityTagsCollectionEnumeratorFactory<T>.Enumerate(activityLink.Tags, ref tagEnumerator);
}

/// <summary>
/// Enumerates all the <see cref="ActivityEvent"/>s on an <see cref="Activity"/> without performing an allocation.
/// </summary>
Expand All @@ -143,6 +157,20 @@ public static void EnumerateEvents<T>(this Activity activity, ref T eventEnumera
ActivityEventsEnumeratorFactory<T>.Enumerate(activity, ref eventEnumerator);
}

/// <summary>
/// Enumerates all the key/value pairs on an <see cref="ActivityEvent"/> without performing an allocation.
/// </summary>
/// <typeparam name="T">The struct <see cref="IActivityEnumerator{T}"/> implementation to use for the enumeration.</typeparam>
/// <param name="activityEvent">ActivityEvent instance.</param>
/// <param name="tagEnumerator">Tag enumerator.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "ActivityProcessor is hot path")]
public static void EnumerateTags<T>(this ActivityEvent activityEvent, ref T tagEnumerator)
where T : struct, IActivityEnumerator<KeyValuePair<string, object>>
{
ActivityTagsCollectionEnumeratorFactory<T>.Enumerate(activityEvent.Tags, ref tagEnumerator);
}

/// <summary>
/// Record Exception.
/// </summary>
Expand Down Expand Up @@ -308,5 +336,36 @@ public static void Enumerate(Activity activity, ref TState state)
private static bool ForEachEventCallback(ref TState state, ActivityEvent item)
=> state.ForEach(item);
}

private static class ActivityTagsCollectionEnumeratorFactory<TState>
where TState : struct, IActivityEnumerator<KeyValuePair<string, object>>
{
private static readonly object EmptyActivityEventTags = typeof(ActivityEvent).GetField("s_emptyTags", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null);

private static readonly DictionaryEnumerator<string, object, TState>.AllocationFreeForEachDelegate
ActivityTagsCollectionEnumerator = DictionaryEnumerator<string, object, TState>.BuildAllocationFreeForEachDelegate(typeof(ActivityTagsCollection));

private static readonly DictionaryEnumerator<string, object, TState>.ForEachDelegate ForEachTagValueCallbackRef = ForEachTagValueCallback;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Enumerate(IEnumerable<KeyValuePair<string, object>> tags, ref TState state)
{
if (ReferenceEquals(tags, EmptyActivityEventTags) || tags is null)
{
// Notes:
// * ActivityEvents.Tags returns s_emptyTags when no ActivityTagsCollection is provided.
// * ActivityLinks.Tags returns null when no ActivityTagsCollection is provided.
return;
}

ActivityTagsCollectionEnumerator(
tags,
ref state,
ForEachTagValueCallbackRef);
}

private static bool ForEachTagValueCallback(ref TState state, KeyValuePair<string, object> item)
=> state.ForEach(item);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,14 @@ internal static class JaegerActivityExtensions
[SemanticConventions.AttributeDbInstance] = 2, // peer.service for Redis.
};

private static readonly DictionaryEnumerator<string, object, PooledListState<JaegerTag>>.ForEachDelegate ProcessTagRef = ProcessTag;

public static JaegerSpan ToJaegerSpan(this Activity activity)
{
var jaegerTags = new TagEnumerationState
{
Tags = PooledList<JaegerTag>.Create(),
};

activity.EnumerateTagValues(ref jaegerTags);
activity.EnumerateTags(ref jaegerTags);

string peerServiceName = null;
if (activity.Kind == ActivityKind.Client || activity.Kind == ActivityKind.Producer)
Expand Down Expand Up @@ -178,24 +176,20 @@ public static PooledList<JaegerLog> ToJaegerLogs(this Activity activity)

public static JaegerLog ToJaegerLog(this ActivityEvent timedEvent)
{
var tags = new PooledListState<JaegerTag>
var jaegerTags = new EventTagsEnumerationState
{
Created = true,
List = PooledList<JaegerTag>.Create(),
Tags = PooledList<JaegerTag>.Create(),
};

DictionaryEnumerator<string, object, PooledListState<JaegerTag>>.AllocationFreeForEach(
timedEvent.Tags,
ref tags,
ProcessTagRef);
timedEvent.EnumerateTags(ref jaegerTags);

// Matches what OpenTracing and OpenTelemetry defines as the event name.
// https://github.com/opentracing/specification/blob/master/semantic_conventions.md#log-fields-table
// https://github.com/open-telemetry/opentelemetry-specification/pull/397/files
PooledList<JaegerTag>.Add(ref tags.List, new JaegerTag("message", JaegerTagType.STRING, vStr: timedEvent.Name));
PooledList<JaegerTag>.Add(ref jaegerTags.Tags, new JaegerTag("message", JaegerTagType.STRING, vStr: timedEvent.Name));

// TODO: Use the same function as JaegerConversionExtensions or check that the perf here is acceptable.
return new JaegerLog(timedEvent.Timestamp.ToEpochMicroseconds(), tags.List);
return new JaegerLog(timedEvent.Timestamp.ToEpochMicroseconds(), jaegerTags.Tags);
}

public static JaegerSpanRef ToJaegerSpanRef(this in ActivityLink link)
Expand Down Expand Up @@ -306,20 +300,6 @@ private static void ProcessJaegerTag(ref TagEnumerationState state, string key,
PooledList<JaegerTag>.Add(ref state.Tags, jaegerTag);
}

private static bool ProcessTag(ref PooledListState<JaegerTag> state, KeyValuePair<string, object> attribute)
{
if (attribute.Value is Array)
{
ProcessJaegerTagArray(ref state.List, attribute);
}
else if (attribute.Value != null)
{
PooledList<JaegerTag>.Add(ref state.List, attribute.ToJaegerTag());
}

return true;
}

private struct TagEnumerationState : IActivityEnumerator<KeyValuePair<string, object>>
{
public PooledList<JaegerTag> Tags;
Expand Down Expand Up @@ -389,11 +369,23 @@ public bool ForEach(ActivityEvent activityEvent)
}
}

private struct PooledListState<T>
private struct EventTagsEnumerationState : IActivityEnumerator<KeyValuePair<string, object>>
{
public bool Created;
public PooledList<JaegerTag> Tags;

public bool ForEach(KeyValuePair<string, object> tag)
{
if (tag.Value is Array)
{
ProcessJaegerTagArray(ref this.Tags, tag);
}
else if (tag.Value != null)
{
PooledList<JaegerTag>.Add(ref this.Tags, tag.ToJaegerTag());
}

public PooledList<T> List;
return true;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ internal static ZipkinSpan ToZipkinSpan(this Activity activity, ZipkinEndpoint d
Tags = PooledList<KeyValuePair<string, object>>.Create(),
};

activity.EnumerateTagValues(ref tagState);
activity.EnumerateTags(ref tagState);

var activitySource = activity.Source;
if (!string.IsNullOrEmpty(activitySource.Name))
Expand Down
21 changes: 2 additions & 19 deletions src/OpenTelemetry/Internal/EnumerationHelper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// <auto-generated>
// <auto-generated>
// <copyright file="EnumerationHelper.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
Expand All @@ -16,9 +16,7 @@
// </copyright>
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;

Expand Down Expand Up @@ -55,8 +53,6 @@ internal class Enumerator<TEnumerable, TItem, TState>
private static readonly MethodInfo GeneircCurrentGetMethod = typeof(IEnumerator<TItem>).GetProperty("Current").GetMethod;
private static readonly MethodInfo MoveNextMethod = typeof(IEnumerator).GetMethod("MoveNext");
private static readonly MethodInfo DisposeMethod = typeof(IDisposable).GetMethod("Dispose");
private static readonly ConcurrentDictionary<Type, AllocationFreeForEachDelegate> AllocationFreeForEachDelegates = new ConcurrentDictionary<Type, AllocationFreeForEachDelegate>();
private static readonly Func<Type, AllocationFreeForEachDelegate> BuildAllocationFreeForEachDelegateRef = BuildAllocationFreeForEachDelegate;

public delegate void AllocationFreeForEachDelegate(TEnumerable instance, ref TState state, ForEachDelegate itemCallback);

Expand All @@ -66,19 +62,6 @@ protected Enumerator()
{
}

public static void AllocationFreeForEach(TEnumerable instance, ref TState state, ForEachDelegate itemCallback)
{
Debug.Assert(instance != null && itemCallback != null);

var type = instance.GetType();

var allocationFreeForEachDelegate = AllocationFreeForEachDelegates.GetOrAdd(
type,
BuildAllocationFreeForEachDelegateRef);

allocationFreeForEachDelegate(instance, ref state, itemCallback);
}

/* We want to do this type of logic...
public static void AllocationFreeForEach(Dictionary<string, int> dictionary, ref TState state, ForEachDelegate itemCallback)
{
Expand Down Expand Up @@ -108,7 +91,7 @@ public static AllocationFreeForEachDelegate BuildAllocationFreeForEachDelegate(T
var enumeratorType = getEnumeratorMethod.ReturnType;

var dynamicMethod = new DynamicMethod(
nameof(AllocationFreeForEach),
nameof(AllocationFreeForEachDelegate),
null,
new[] { typeof(TEnumerable), typeof(TState).MakeByRefType(), itemCallbackType },
typeof(AllocationFreeForEachDelegate).Module,
Expand Down
63 changes: 56 additions & 7 deletions test/Benchmarks/ActivityBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ public class ActivityBenchmarks
{
private static readonly Activity EmptyActivity = new Activity("EmptyActivity");
private static readonly Activity Activity;
private static readonly ActivityLink ActivityLink;
private static readonly ActivityEvent ActivityEvent;

static ActivityBenchmarks()
{
Expand All @@ -39,11 +41,20 @@ static ActivityBenchmarks()
Sample = (ref ActivityCreationOptions<ActivityContext> options) => ActivitySamplingResult.AllData,
});

var activityTagCollection = new ActivityTagsCollection(new Dictionary<string, object>
{
["tag1"] = "value1",
["tag2"] = "value2",
["tag3"] = "value3",
});

ActivityLink = new ActivityLink(default, activityTagCollection);

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

Activity = activitySource.StartActivity(
Expand All @@ -61,9 +72,11 @@ static ActivityBenchmarks()
Activity.AddTag($"AutoTag{i}", i);
}

Activity.AddEvent(new ActivityEvent("event1"));
Activity.AddEvent(new ActivityEvent("event2"));
Activity.AddEvent(new ActivityEvent("event3"));
ActivityEvent = new ActivityEvent("event1", tags: activityTagCollection);

Activity.AddEvent(ActivityEvent);
Activity.AddEvent(new ActivityEvent("event2", tags: activityTagCollection));
Activity.AddEvent(new ActivityEvent("event3", tags: activityTagCollection));

Activity.Stop();
}
Expand Down Expand Up @@ -135,7 +148,7 @@ public void EnumerateTagValuesNonemptyTagObjects()
{
TagEnumerator state = default;

Activity.EnumerateTagValues(ref state);
Activity.EnumerateTags(ref state);
}

[Benchmark]
Expand All @@ -156,6 +169,24 @@ public void EnumerateLinksNonemptyActivityLinks()
Activity.EnumerateLinks(ref state);
}

[Benchmark]
public void EnumerateNonemptyActivityLinkTags()
{
int count = 0;
foreach (var tag in ActivityLink.Tags)
{
count++;
}
}

[Benchmark]
public void EnumerateLinksNonemptyActivityLinkTags()
{
TagEnumerator state = default;

ActivityLink.EnumerateTags(ref state);
}

[Benchmark]
public void EnumerateNonemptyActivityEvents()
{
Expand All @@ -174,6 +205,24 @@ public void EnumerateEventsNonemptyActivityEvents()
Activity.EnumerateEvents(ref state);
}

[Benchmark]
public void EnumerateNonemptyActivityEventTags()
{
int count = 0;
foreach (var tag in ActivityEvent.Tags)
{
count++;
}
}

[Benchmark]
public void EnumerateLinksNonemptyActivityEventTags()
{
TagEnumerator state = default;

ActivityEvent.EnumerateTags(ref state);
}

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