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.CreateActivity methods #48374

Merged
merged 4 commits into from
Feb 23, 2021

Conversation

tarekgh
Copy link
Member

@tarekgh tarekgh commented Feb 17, 2021

Fixes #42784

This PR should be addressing #43853 too.

@dotnet-issue-labeler
Copy link

Note regarding the new-api-needs-documentation label:

This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, to please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change.

@ghost
Copy link

ghost commented Feb 17, 2021

Tagging subscribers to this area: @tarekgh
See info in area-owners.md if you want to be subscribed.

Issue Details

Fixes #42784

This PR should be addressing #43853 too.

Author: tarekgh
Assignees: -
Labels:

area-System.Diagnostics.Activity, new-api-needs-documentation

Milestone: -

@tarekgh
Copy link
Member Author

tarekgh commented Feb 17, 2021

#47313

@tarekgh tarekgh requested a review from noahfalk February 17, 2021 01:16
activity.StartTimeUtc = startTime.UtcDateTime;
}

if (startIt)
Copy link
Member

Choose a reason for hiding this comment

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

Does anything prevent us from doing?

Suggested change
if (startIt)
if (startIt)
{
Start();
}

It feels odd to be replicating all this start code.

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 think it is possible. The only different case would be when someone calling StartActivity and passing ActivityContext with a default span Id. we used to keep Activity.Parent=null but if we call Activity.Start() it would set Activity.Parent to the Activity.Current. I am not sure though if this will be ok (or should be the right behavior). what you think?

Copy link
Member

Choose a reason for hiding this comment

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

In theory spanId = 0 is an invalid parent context. We should probably take a peak at the spec and chat with the OT folks to see if they have any input on what error handling behavior is desirable. I don't recall us ever defining the behavior very precisely but what I see the code currently do is give the bad id (in string or ActivityContext form) to the sampler as-is. Later in CreateAndStart we potentially ignore the id (if it was in string form) and create a new id based on IdFormat. This runs afoul of our sampler TraceId == Activity.TraceId invariant the same as above.

I don't care too much about the factoring on its own, but I am glad thinking about it sussed out issues like this one : )

@@ -146,6 +146,9 @@ public sealed class ActivitySource : IDisposable
public string Name { get { throw null; } }
public string? Version { 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) { throw null; }
public System.Diagnostics.Activity? CreateActivity(string name, System.Diagnostics.ActivityKind kind, string parentId, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, object?>>? tags = null, System.Collections.Generic.IEnumerable<System.Diagnostics.ActivityLink>? links = null) { throw null; }
Copy link
Member

Choose a reason for hiding this comment

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

Weren't we going to handle #43853 by adding an IdFormat parameter here?

Copy link
Member Author

@tarekgh tarekgh Feb 17, 2021

Choose a reason for hiding this comment

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

No. After discussing this in the design review, @pakrym felt the other proposal is not needed and that can be handled by this CreateActivity proposal. After creating the activity, they can set the IdFormat on such created Activity before starting it.

Copy link
Member

Choose a reason for hiding this comment

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

Unforetunately that doesn't work. CreateActivity() calls the sampling callback, sampling callback has access to the ID, generating that ID has already fixed the id format. Using SetIdFormat() after CreateActivity() should probably throw an exception if it doesn't already.

Copy link
Member Author

@tarekgh tarekgh Feb 17, 2021

Choose a reason for hiding this comment

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

        public Activity SetIdFormat(ActivityIdFormat format)
        {
            if (_id != null || _spanId != null)
            {
                NotifyError(new InvalidOperationException(SR.SetFormatOnStartedActivity));
            }
            else
            {
                IdFormat = format;
            }
            return this;
        }

If the Activity not started, _id and _spanId would still be nulls. otherwise Activity.Start() would fail too. no?

Copy link
Member Author

@tarekgh tarekgh Feb 17, 2021

Choose a reason for hiding this comment

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

@noahfalk I have addressed all comments. The remaining point is this one which related to IdFormat. I don't think we should fix the Ids before starting the Activity. Please let me know if you think otherwise.

Copy link
Contributor

Choose a reason for hiding this comment

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

The caller is passing default context, there is no parent (Activity.Current), and the listeners didn't try to access the trace Id from the provided ActivityCreationOptions.

This case seems very real. Think of a worker app uploading a blob. There is no parent and all spans are exported so TraceId is not accessed.

Copy link
Member Author

Choose a reason for hiding this comment

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

This case seems very real. Think of a worker app uploading a blob. There is no parent and all spans are exported so TraceId is not accessed.

Yes, agree. But using CreateActivity with the current design will make it work. right? I mean users can use CreateActivity and SetIdFormat if they need to for this scenario.

Copy link
Member Author

@tarekgh tarekgh Feb 18, 2021

Choose a reason for hiding this comment

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

@pakrym I had offline discussion with @noahfalk and we propose adding extra Boolean parameter to the CreateActivity called forceW3CId. The reason we didn't have the parameter of ActivityIdFormat type because we want to promote W3C in general and not allow users to try the hierarchal explicitly. Please let's know if you have any concern. I'll send it anyway to the fxdc and will CC you there.

        public Activity? CreateActivity(string name, 
                                        ActivityKind kind);

        public Activity? CreateActivity(
                             string name,
                             ActivityKind kind, 
                             ActivityContext parentContext,
                             IEnumerable<KeyValuePair<string, object?>>? tags = null, 
                             IEnumerable<ActivityLink>? links = null,
                             bool forceW3CId = false); // added parameter

        public Activity? CreateActivity(
                             string name, 
                             ActivityKind kind,
                             string parentContext,
                             bool forceW3CId = false,
                             IEnumerable<KeyValuePair<string, object?>>? tags = null, 
                             IEnumerable<ActivityLink>? links = null,
                             bool forceW3CId = false);     // added parameter

Copy link
Contributor

Choose a reason for hiding this comment

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

LGTM. If we want to promote W3C can we default the parameter to true? 😄

Copy link
Member Author

Choose a reason for hiding this comment

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

If we want to promote W3C can we default the parameter to true? 😄

I discussed that with @noahfalk and he preferred not to default the value to W3C as he want to have consistent behavior with StartActivity when passing the exact same parameters.

Copy link
Member

@noahfalk noahfalk left a comment

Choose a reason for hiding this comment

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

Mostly looked fine but I think we need to fix the closure allocation


// delegate to ensure updating the trace id in other ActivityCreationOption object when a new trace Id is generated.
// It is important so all listeners callbacks will always see the same trace Id which the activity will get created with
Action<ActivityTraceId, bool> updateTraceId = (traceId, isChangedFromContext) =>
Copy link
Member

Choose a reason for hiding this comment

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

This delegate needs a closure so it will do an allocation that we don't want. However we have a few ways out:

  1. Previously the behavior for ActivityCreationOptions<string>.TraceId is that it always returns default. I suggest we preserve that behavior so there is no need to copy the TraceId across the different contexts.
  2. Previously generating the TraceId was expensive because our random number generator was slow. Maybe now it is fast enough that there is little advantage in trying to generate it lazily? We'd need a benchmark measurement to know for sure.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, I'll go with the #1 to make ActivityCreationOptions<string>.TraceId return default. I'll remove the delegate from the picture.

// - If the parentId string is null and parent ActivityContext is default and we have Activity.Current != null, then use Activity.Current.IdFormat. Otherwise, goto next step.
// - If we have non default parent ActivityContext, then use W3C. Otherwise, goto next step.
// - if we have non null parent id string, try to parse it to W3C. If parsing succeeded then use W3C format. otherwise use Hierarchical format.
// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Copy link
Member

Choose a reason for hiding this comment

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

There is a missing case in the algorithm where we should choose to use default format id:

  • IdFormat passed to ActivitySource.Create is Unknown (step 1 doesn't apply)
  • ForceDefaultFormat = false (step 2 doesn't apply)
  • Activity.Current = null (step 3 doesn't apply)
  • parent ActivityContext/id are default (steps 4 and 5 don't apply)

Copy link
Member Author

Choose a reason for hiding this comment

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

right. I handled this in the code :-) but didn't specify this step here. I'll add it here.

[InlineData($"Unknown, Unknown, W3C, null, true, false, W3C, W3C")]
[InlineData($"Unknown, Unknown, Hierarchical, null, true, false, Hierarchical, Hierarchical")]

[InlineData($"Unknown, Unknown, Unknown, NonWC3Id, true, false, Hierarchical, Hierarchical")]
Copy link
Member

Choose a reason for hiding this comment

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

Nit - the constants were inconsistent in the column:

Suggested change
[InlineData($"Unknown, Unknown, Unknown, NonWC3Id, true, false, Hierarchical, Hierarchical")]
[InlineData($"Unknown, Unknown, Unknown, NonWC3, true, false, Hierarchical, Hierarchical")]

Copy link
Member Author

Choose a reason for hiding this comment

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

will fix that.

// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------

// -----------------------------------------------------------------------------------------------------------------------------------------------------------------
// Default Id Format, Id fromat to force, Parent id format, parent id, default parent context, validate trace Id, expected create format, expected start format
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// Default Id Format, Id fromat to force, Parent id format, parent id, default parent context, validate trace Id, expected create format, expected start format
// Activity.DefaultIdFormat, Activity.Create(idFormat), Activity.Current.IdFormat, parent id, default parent context, validate trace Id, expected create format, expected start format
// (ForceIdFormat=true)

I got the wrong idea about what these columns meant until I reverse engineered it from the source. This might help clarify the intent.

Copy link
Member Author

Choose a reason for hiding this comment

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

will do


using ActivityListener listener = new ActivityListener();
listener.ShouldListenTo = (activitySource) => object.ReferenceEquals(aSource, activitySource);
listener.SampleUsingParentId = (ref ActivityCreationOptions<string> activityOptions) =>
Copy link
Member

Choose a reason for hiding this comment

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

Lets add a 2nd listener that only implements the Sample callback. This will let us confirm that Sample is always called for W3C formatted activities and never called for Hierarchal ones.

[InlineData($"Unknown, Hierarchical, Unknown, null, false, false, Hierarchical, W3C")]
[InlineData($"Unknown, W3C, Unknown, null, false, false, W3C, W3C")]

[InlineData($"Unknown, W3C, Unknown, null, true, true, W3C, Default")]
Copy link
Member

Choose a reason for hiding this comment

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

We should have the test case where a non-W3C string id gets passed as the parentId, the child does get W3C format, and the ActivityCreationOptions<ActivityContext>.TraceId property does get viewed inside the Sample callback. That case wasn't previously calling Sample and we need to ensure that now it does work correctly.

Copy link
Member Author

Choose a reason for hiding this comment

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

You mean when the listener is not providing SampleUsingParentId. right?

Copy link
Member

Choose a reason for hiding this comment

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

Right!

listener.SampleUsingParentId = (ref ActivityCreationOptions<string> activityOptions) =>
{
if (checkTraceId)
traceId = activityOptions.TraceId;
Copy link
Member

Choose a reason for hiding this comment

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

Assuming we keep the previous behavior for SampleWithParentId, activityOptions.TraceId should always return default here.

Copy link
Member Author

Choose a reason for hiding this comment

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

To clarify, it will return default only in the case the parent Id string cannot parsed to W3C. right?

Copy link
Member

Choose a reason for hiding this comment

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

I was looking at the code more closely and may have misinterpretted the old behavior, trying to review more closely now : )

Copy link
Member

Choose a reason for hiding this comment

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

I did misinterpret and regardless I think the way you changed it will be fine. The behavior of ActivityCreationOptions<string>.TraceId is a bit weird, but this matches it's previous weird behavior and breaking change isn't necessarily any better : )

[InlineData($"Unknown, Unknown, Unknown, null, false, true, W3C, W3C")]

[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
public void TestIdFormats(string data)
Copy link
Member

Choose a reason for hiding this comment

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

Nice test : )

@tarekgh
Copy link
Member Author

tarekgh commented Feb 22, 2021

@noahfalk I believe I have addressed all your feedback with the last commit. please let me know if you have any more comments or we good to go. Thanks!

Copy link
Member

@noahfalk noahfalk left a comment

Choose a reason for hiding this comment

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

LGTM, thanks Tarek!

@tarekgh tarekgh merged commit 970d347 into dotnet:master Feb 23, 2021
@tarekgh tarekgh deleted the AddActivitySourceCreateMethods branch February 23, 2021 00:49
@ghost ghost locked as resolved and limited conversation to collaborators Mar 25, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow setting the Activity properties after Activity creation and before starting it when using ActivitySource
3 participants