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

OTLP exporter support for limiting activity tags, events, and links #3376

Merged
merged 16 commits into from
Jul 28, 2022
Merged
Show file tree
Hide file tree
Changes from 13 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
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)
cijothomas marked this conversation as resolved.
Show resolved Hide resolved
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)
Copy link
Contributor

Choose a reason for hiding this comment

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

If we don't go with the overload approach, then maybe assign maxTags a default value of null.

{
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
cijothomas marked this conversation as resolved.
Show resolved Hide resolved
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
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