Skip to content

Commit

Permalink
OTLP exporter support for limiting activity tags, events, and links (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
alanwest authored Jul 28, 2022
1 parent 25cfa17 commit c1f376a
Show file tree
Hide file tree
Showing 8 changed files with 449 additions and 35 deletions.
102 changes: 84 additions & 18 deletions src/OpenTelemetry.Api/Internal/ActivityHelperExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public static bool TryGetStatus(this Activity activity, out StatusCode statusCod

ActivityStatusTagEnumerator state = default;

ActivityTagsEnumeratorFactory<ActivityStatusTagEnumerator>.Enumerate(activity, ref state);
ActivityTagsEnumeratorFactory<ActivityStatusTagEnumerator>.Enumerate(activity, ref state, null);

if (!state.StatusCode.HasValue)
{
Expand Down Expand Up @@ -72,7 +72,7 @@ public static object GetTagValue(this Activity activity, string tagName)

ActivitySingleTagEnumerator state = new ActivitySingleTagEnumerator(tagName);

ActivityTagsEnumeratorFactory<ActivitySingleTagEnumerator>.Enumerate(activity, ref state);
ActivityTagsEnumeratorFactory<ActivitySingleTagEnumerator>.Enumerate(activity, ref state, null);

return state.Value;
}
Expand All @@ -91,7 +91,7 @@ public static bool TryCheckFirstTag(this Activity activity, string tagName, out

ActivityFirstTagEnumerator state = new ActivityFirstTagEnumerator(tagName);

ActivityTagsEnumeratorFactory<ActivityFirstTagEnumerator>.Enumerate(activity, ref state);
ActivityTagsEnumeratorFactory<ActivityFirstTagEnumerator>.Enumerate(activity, ref state, null);

if (state.Value == null)
{
Expand All @@ -109,14 +109,15 @@ public static bool TryCheckFirstTag(this Activity activity, string tagName, out
/// <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>
/// <param name="maxTags">Maximum number of tags to enumerate.</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 Activity activity, ref T tagEnumerator)
public static void EnumerateTags<T>(this Activity activity, ref T tagEnumerator, int? maxTags = null)
where T : struct, IActivityEnumerator<KeyValuePair<string, object>>
{
Debug.Assert(activity != null, "Activity should not be null");

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

/// <summary>
Expand All @@ -125,14 +126,15 @@ public static void EnumerateTags<T>(this Activity activity, ref T tagEnumerator)
/// <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">Link enumerator.</param>
/// <param name="maxLinks">Maximum number of links to enumerate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "ActivityProcessor is hot path")]
public static void EnumerateLinks<T>(this Activity activity, ref T linkEnumerator)
public static void EnumerateLinks<T>(this Activity activity, ref T linkEnumerator, int? maxLinks = null)
where T : struct, IActivityEnumerator<ActivityLink>
{
Debug.Assert(activity != null, "Activity should not be null");

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

/// <summary>
Expand All @@ -141,12 +143,13 @@ public static void EnumerateLinks<T>(this Activity activity, ref T linkEnumerato
/// <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>
/// <param name="maxTags">Maximum number of tags to enumerate.</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)
public static void EnumerateTags<T>(this ActivityLink activityLink, ref T tagEnumerator, int? maxTags = null)
where T : struct, IActivityEnumerator<KeyValuePair<string, object>>
{
ActivityTagsEnumeratorFactory<T>.Enumerate(activityLink, ref tagEnumerator);
ActivityTagsEnumeratorFactory<T>.Enumerate(activityLink, ref tagEnumerator, maxTags);
}

/// <summary>
Expand All @@ -155,14 +158,15 @@ public static void EnumerateTags<T>(this ActivityLink activityLink, ref T tagEnu
/// <typeparam name="T">The struct <see cref="IActivityEnumerator{T}"/> implementation to use for the enumeration.</typeparam>
/// <param name="activity">Activity instance.</param>
/// <param name="eventEnumerator">Event enumerator.</param>
/// <param name="maxEvents">Maximum number of events to enumerate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "ActivityProcessor is hot path")]
public static void EnumerateEvents<T>(this Activity activity, ref T eventEnumerator)
public static void EnumerateEvents<T>(this Activity activity, ref T eventEnumerator, int? maxEvents = null)
where T : struct, IActivityEnumerator<ActivityEvent>
{
Debug.Assert(activity != null, "Activity should not be null");

ActivityEventsEnumeratorFactory<T>.Enumerate(activity, ref eventEnumerator);
ActivityEventsEnumeratorFactory<T>.Enumerate(activity, ref eventEnumerator, maxEvents);
}

/// <summary>
Expand All @@ -171,12 +175,13 @@ public static void EnumerateEvents<T>(this Activity activity, ref T eventEnumera
/// <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>
/// <param name="maxTags">Maximum number of tags to enumerate.</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)
public static void EnumerateTags<T>(this ActivityEvent activityEvent, ref T tagEnumerator, int? maxTags = null)
where T : struct, IActivityEnumerator<KeyValuePair<string, object>>
{
ActivityTagsEnumeratorFactory<T>.Enumerate(activityEvent, ref tagEnumerator);
ActivityTagsEnumeratorFactory<T>.Enumerate(activityEvent, ref tagEnumerator, maxTags);
}

private struct ActivitySingleTagEnumerator : IActivityEnumerator<KeyValuePair<string, object>>
Expand Down Expand Up @@ -265,7 +270,7 @@ private static readonly DictionaryEnumerator<string, object, TState>.AllocationF
private static readonly DictionaryEnumerator<string, object, TState>.ForEachDelegate ForEachTagValueCallbackRef = ForEachTagValueCallback;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Enumerate(Activity activity, ref TState state)
public static void Enumerate(Activity activity, ref TState state, int? maxTags)
{
var tagObjects = activity.TagObjects;

Expand All @@ -274,14 +279,20 @@ public static void Enumerate(Activity activity, ref TState state)
return;
}

if (maxTags.HasValue)
{
SkipAllocationFreeEnumeration(tagObjects, ref state, maxTags.Value);
return;
}

ActivityTagObjectsEnumerator(
tagObjects,
ref state,
ForEachTagValueCallbackRef);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Enumerate(ActivityLink activityLink, ref TState state)
public static void Enumerate(ActivityLink activityLink, ref TState state, int? maxTags)
{
var tags = activityLink.Tags;

Expand All @@ -290,14 +301,20 @@ public static void Enumerate(ActivityLink activityLink, ref TState state)
return;
}

if (maxTags.HasValue)
{
SkipAllocationFreeEnumeration(tags, ref state, maxTags.Value);
return;
}

ActivityTagsCollectionEnumerator(
tags,
ref state,
ForEachTagValueCallbackRef);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Enumerate(ActivityEvent activityEvent, ref TState state)
public static void Enumerate(ActivityEvent activityEvent, ref TState state, int? maxTags)
{
var tags = activityEvent.Tags;

Expand All @@ -306,12 +323,31 @@ public static void Enumerate(ActivityEvent activityEvent, ref TState state)
return;
}

if (maxTags.HasValue)
{
SkipAllocationFreeEnumeration(tags, ref state, maxTags.Value);
return;
}

ActivityTagsCollectionEnumerator(
tags,
ref state,
ForEachTagValueCallbackRef);
}

// TODO: When a limit has been configured an allocation-free enumerator is not used.
// Need to either:
// 1) modify the dynamically generated code to only enumerate up to the max number of items, or
// 2) wait until .NET 7 is released and do this more easily with the new enumerator functions
private static void SkipAllocationFreeEnumeration(IEnumerable<KeyValuePair<string, object>> tags, ref TState state, int maxTags)
{
var enumerator = tags.GetEnumerator();
for (var i = 0; enumerator.MoveNext() && i < maxTags; ++i)
{
state.ForEach(enumerator.Current);
}
}

private static bool ForEachTagValueCallback(ref TState state, KeyValuePair<string, object> item)
=> state.ForEach(item);
}
Expand All @@ -328,7 +364,7 @@ private static readonly ListEnumerator<ActivityLink, TState>.AllocationFreeForEa
private static readonly ListEnumerator<ActivityLink, TState>.ForEachDelegate ForEachLinkCallbackRef = ForEachLinkCallback;

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

Expand All @@ -337,6 +373,21 @@ public static void Enumerate(Activity activity, ref TState state)
return;
}

// TODO: When a limit has been configured an allocation-free enumerator is not used.
// Need to either:
// 1) modify the dynamically generated code to only enumerate up to the max number of items, or
// 2) wait until .NET 7 is released and do this more easily with the new enumerator functions
if (maxLinks.HasValue)
{
var enumerator = activityLinks.GetEnumerator();
for (var i = 0; enumerator.MoveNext() && i < maxLinks; ++i)
{
state.ForEach(enumerator.Current);
}

return;
}

ActivityLinksEnumerator(
activityLinks,
ref state,
Expand All @@ -359,7 +410,7 @@ private static readonly ListEnumerator<ActivityEvent, TState>.AllocationFreeForE
private static readonly ListEnumerator<ActivityEvent, TState>.ForEachDelegate ForEachEventCallbackRef = ForEachEventCallback;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Enumerate(Activity activity, ref TState state)
public static void Enumerate(Activity activity, ref TState state, int? maxEvents)
{
var activityEvents = activity.Events;

Expand All @@ -368,6 +419,21 @@ public static void Enumerate(Activity activity, ref TState state)
return;
}

// TODO: When a limit has been configured an allocation-free enumerator is not used.
// Need to either:
// 1) modify the dynamically generated code to only enumerate up to the max number of items, or
// 2) wait until .NET 7 is released and do this more easily with the new enumerator functions
if (maxEvents.HasValue)
{
var enumerator = activityEvents.GetEnumerator();
for (var i = 0; enumerator.MoveNext() && i < maxEvents; ++i)
{
state.ForEach(enumerator.Current);
}

return;
}

ActivityEventsEnumerator(
activityEvents,
ref state,
Expand Down
6 changes: 6 additions & 0 deletions src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

## Unreleased

* Adds support for limiting the length and count of attributes exported from
the OTLP exporter. These
[Attribute Limits](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md#attribute-limits)
are configured via the environment variables defined in the specification.
([#3376](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3376))

* The `MetricReaderOptions` defaults can be overridden using
`OTEL_METRIC_EXPORT_INTERVAL` and `OTEL_METRIC_EXPORT_TIMEOUT`
environmental variables as defined in the
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// <copyright file="EnvironmentVariableConfiguration.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

using System;
using OpenTelemetry.Internal;

namespace OpenTelemetry.Configuration;

internal class EnvironmentVariableConfiguration
{
public static void InitializeDefaultConfigurationFromEnvironment(SdkConfiguration sdkConfiguration)
{
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md#attribute-limits
SetIntConfigValue("OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT", value => sdkConfiguration.AttributeValueLengthLimit = value);
SetIntConfigValue("OTEL_ATTRIBUTE_COUNT_LIMIT", value => sdkConfiguration.AttributeCountLimit = value);

// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md#span-limits
SetIntConfigValue("OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT", value => sdkConfiguration.SpanAttributeValueLengthLimit = value);
SetIntConfigValue("OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT", value => sdkConfiguration.SpanAttributeCountLimit = value);
SetIntConfigValue("OTEL_SPAN_EVENT_COUNT_LIMIT", value => sdkConfiguration.SpanEventCountLimit = value);
SetIntConfigValue("OTEL_SPAN_LINK_COUNT_LIMIT", value => sdkConfiguration.SpanLinkCountLimit = value);
SetIntConfigValue("OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT", value => sdkConfiguration.EventAttributeCountLimit = value);
SetIntConfigValue("OTEL_LINK_ATTRIBUTE_COUNT_LIMIT", value => sdkConfiguration.LinkAttributeCountLimit = value);
}

private static void SetIntConfigValue(string key, Action<int> setter)
{
if (EnvironmentVariableHelper.LoadNumeric(key, out var result))
{
setter(result);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// <copyright file="SdkConfiguration.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

namespace OpenTelemetry.Configuration;

internal class SdkConfiguration
{
private int? spanAttributeValueLengthLimit;
private int? spanAttributeCountLimit;
private int? eventAttributeCountLimit;
private int? linkAttributeCountLimit;

private SdkConfiguration()
{
EnvironmentVariableConfiguration.InitializeDefaultConfigurationFromEnvironment(this);
}

public static SdkConfiguration Instance { get; private set; } = new SdkConfiguration();

public int? AttributeValueLengthLimit { get; set; }

public int? AttributeCountLimit { get; set; }

public int? SpanAttributeValueLengthLimit
{
get => this.spanAttributeValueLengthLimit ?? this.AttributeValueLengthLimit;
set => this.spanAttributeValueLengthLimit = value;
}

public int? SpanAttributeCountLimit
{
get => this.spanAttributeCountLimit ?? this.AttributeCountLimit;
set => this.spanAttributeCountLimit = value;
}

public int? SpanEventCountLimit { get; set; }

public int? SpanLinkCountLimit { get; set; }

public int? EventAttributeCountLimit
{
get => this.eventAttributeCountLimit ?? this.SpanAttributeCountLimit;
set => this.eventAttributeCountLimit = value;
}

public int? LinkAttributeCountLimit
{
get => this.linkAttributeCountLimit ?? this.SpanAttributeCountLimit;
set => this.linkAttributeCountLimit = value;
}

internal static void Reset()
{
Instance = new SdkConfiguration();
}
}
Loading

0 comments on commit c1f376a

Please sign in to comment.