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

Add ActivitySource Tags #102678

Merged
merged 2 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ internal static class DiagnosticsHelper
/// we avoid the allocation of a new array by using the second collection as is and not converting it to an array. the reason
/// is we call this every time we try to create a meter or instrument and we don't want to allocate a new array every time.
/// </remarks>
internal static bool CompareTags(List<KeyValuePair<string, object?>>? sortedTags, IEnumerable<KeyValuePair<string, object?>>? tags2)
internal static bool CompareTags(IList<KeyValuePair<string, object?>>? sortedTags, IEnumerable<KeyValuePair<string, object?>>? tags2)
{
if (sortedTags == tags2)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.Metrics;

Expand Down Expand Up @@ -40,7 +41,7 @@ public Meter Create(MeterOptions options)
{
foreach (Meter meter in meterList)
{
if (meter.Version == options.Version && DiagnosticsHelper.CompareTags(meter.Tags as List<KeyValuePair<string, object?>>, options.Tags))
if (meter.Version == options.Version && DiagnosticsHelper.CompareTags(meter.Tags as IList<KeyValuePair<string, object?>>, options.Tags))
{
return meter;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,13 @@ public void CopyTo(System.Span<byte> destination) { }
}
public sealed class ActivitySource : IDisposable
{
public ActivitySource(string name) { throw null; }
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
public ActivitySource(string name, string? version = "") { throw null; }
public ActivitySource(string name, string? version = "", System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>>? tags = default) { throw null; }

Choose a reason for hiding this comment

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

would we need to do the [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] on this one too when we support schemaUrl as a parameter? (#63651)

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, when adding a new constructor with extra optional parameter, we can add the [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] to the existing one.

I was thinking in the future if we add more options, we may start having ActivitySourceOptions and a constructor take this options object.

public string Name { get { throw null; } }
public string? Version { get { throw null; } }
public System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>>? Tags { get { throw null; } }
public bool HasListeners() { throw null; }
public System.Diagnostics.Activity? CreateActivity(string name, System.Diagnostics.ActivityKind kind) { throw null; }
public System.Diagnostics.Activity? CreateActivity(string name, System.Diagnostics.ActivityKind kind, System.Diagnostics.ActivityContext parentContext, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>>? tags = null, System.Collections.Generic.IEnumerable<System.Diagnostics.ActivityLink>? links = null, System.Diagnostics.ActivityIdFormat idFormat = System.Diagnostics.ActivityIdFormat.Unknown) { throw null; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;

Expand All @@ -13,16 +14,40 @@ public sealed class ActivitySource : IDisposable
private static readonly SynchronizedList<ActivityListener> s_allListeners = new SynchronizedList<ActivityListener>();
private SynchronizedList<ActivityListener>? _listeners;

/// <summary>
/// Construct an ActivitySource object with the input name
/// </summary>
/// <param name="name">The name of the ActivitySource object</param>
public ActivitySource(string name) : this(name, version: "", tags: null) {}

/// <summary>
/// Construct an ActivitySource object with the input name
/// </summary>
/// <param name="name">The name of the ActivitySource object</param>
/// <param name="version">The version of the component publishing the tracing info.</param>
public ActivitySource(string name, string? version = "")
[EditorBrowsable(EditorBrowsableState.Never)]
public ActivitySource(string name, string? version = "") : this(name, version, tags: null) {}

/// <summary>
/// Construct an ActivitySource object with the input name
/// </summary>
/// <param name="name">The name of the ActivitySource object</param>
/// <param name="version">The version of the component publishing the tracing info.</param>
/// <param name="tags">The optional ActivitySource tags.</param>
public ActivitySource(string name, string? version = "", IEnumerable<KeyValuePair<string, object?>>? tags = default)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
Version = version;

// Sorting the tags to make sure the tags are always in the same order.
// Sorting can help in comparing the tags used for any scenario.
if (tags is not null)
{
var tagList = new List<KeyValuePair<string, object?>>(tags);
tagList.Sort((left, right) => string.Compare(left.Key, right.Key, StringComparison.Ordinal));
Copy link
Contributor

Choose a reason for hiding this comment

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

what is the usecase for the sorting here?
Also, should this do de-duplication, given Activity Tags are deduped when set via SetTag?

Copy link
Member Author

@tarekgh tarekgh May 25, 2024

Choose a reason for hiding this comment

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

what is the usecase for the sorting here?

We apply the same approach for Meter. The reason is to avoid creating duplicate meters with the same parameters and tags in the cached objects used in DI. While ActivitySource is not currently used in DI, implementing this now ensures consistent behavior and eliminates the need for changes if we decide to support ActivitySource/tracing configuration in the future.

should this do de-duplication, given Activity Tags are deduped when set via SetTag?

Any reason? Should this be the responsibility of the callers? We didn't do that with the Meter tags.

Copy link
Contributor

Choose a reason for hiding this comment

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

should this do de-duplication, given Activity Tags are deduped when set via SetTag?

Any reason? Should this be the responsibility of the callers? We didn't do that with the Meter tags.

Because Activity.SetTag already does de-duplication, setting some precedence!
For metrics, BCL does not do any deduping of tags!

Copy link
Member Author

@tarekgh tarekgh May 28, 2024

Choose a reason for hiding this comment

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

I am fine with this if you see it as important. However, I want to understand the scenario better. Who is going to create a new ActivitySource and potentially have duplicate tags? I don't see this as a likely scenario since the creators of the ActivitySource will know exactly which tags they want to use. SetTag scenario is different as different parties can call it on the same activity object in different time. This is not the case for ActivitySource creation.

Tags = tagList.AsReadOnly();
}

s_activeSources.Add(this);

if (s_allListeners.Count > 0)
Expand Down Expand Up @@ -54,6 +79,11 @@ public ActivitySource(string name, string? version = "")
/// </summary>
public string? Version { get; }

/// <summary>
/// Returns the tags associated with the ActivitySource.
/// </summary>
public IEnumerable<KeyValuePair<string, object?>>? Tags { get; }

/// <summary>
/// Check if there is any listeners for this ActivitySource.
/// This property can be helpful to tell if there is no listener, then no need to create Activity object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ private void Initialize(string name, string? version, IEnumerable<KeyValuePair<s
{
var tagList = new List<KeyValuePair<string, object?>>(tags);
tagList.Sort((left, right) => string.Compare(left.Key, right.Key, StringComparison.Ordinal));
Tags = tagList;
Tags = tagList.AsReadOnly();
}
Scope = scope;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,20 @@ public void TestConstruction()
Assert.Equal("Source1", as1.Name);
Assert.Equal(String.Empty, as1.Version);
Assert.False(as1.HasListeners());
Assert.Null(as1.Tags);

using ActivitySource as2 = new ActivitySource("Source2", "1.1.1.2");
Assert.Equal("Source2", as2.Name);
Assert.Equal("1.1.1.2", as2.Version);
Assert.False(as2.HasListeners());
Assert.Null(as2.Tags);

using ActivitySource as3 = new ActivitySource("Source3", "1.1.1.3", new TagList { { "key3", "value3" }, { "key2", "value2" }, { "key1", "value1" } });
Assert.Equal("Source3", as3.Name);
Assert.Equal("1.1.1.3", as3.Version);
Assert.False(as3.HasListeners());
// Ensure the tags are sorted by key.
Assert.Equal(new TagList { { "key1", "value1" }, { "key2", "value2" }, { "key3", "value3" } }, as3.Tags);
}).Dispose();
}

Expand Down