-
Notifications
You must be signed in to change notification settings - Fork 762
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
CustomProperty improvements #1261
CustomProperty improvements #1261
Conversation
Codecov Report
@@ Coverage Diff @@
## master #1261 +/- ##
==========================================
- Coverage 77.44% 77.05% -0.40%
==========================================
Files 223 223
Lines 6362 6428 +66
==========================================
+ Hits 4927 4953 +26
- Misses 1435 1475 +40
|
Just thinking out loud here. How would this work if you were shipping something that wanted to access the raw objects downstream? For example, let's say we are building a custom ActivityProcessor that adds extra data for http spans. The hosting application owns the options, so our custom ActivityProcessor would have to ask the user through documentation to turn these on in the host before it would work? Ideally we would have a way for it to request that extra data automatically. Could we use the runtime context somehow? |
I guess the goal is to reduce the expensive dictionary operation AND the potential pressure from GC?
|
|
Haven't spent much time thinking about this, I guess we probably would end up with 1 key, which contains a linked list?
It is when the last processor.OnEnd returned. |
If we use runtimecontext, so we wouldn't need the flag at all, right? |
|
@reyang @CodeBlanch , pushed one change moving to RuntimeContextSlot. Still a working in progress. But, the general idea:
My thoughts:
|
@eddynaka Esstentially what I'm saying is you can remove the cleanup part and switch to:
(@reyang Can you confirm the cleanup part?) BUT, I don't think we have really accomplished anything. We just switched allocation from Activity to RuntimeContext. What we really need is a way for some library component (ActivityProcessor, Exporter, Instrumentation, etc) or user to opt into the behavior globally and then the instrumentation adds it based on the behavior being triggered? |
One more thing I just realized...
We set the |
Applying the change to Before (master branch):
Actual (Runtime Context):
|
I think, in general, the idea of the flag was to prevent to write somewhere (activity, rutimecontext, etc). With that, if we had that flag as false, we would have less allocations (theoretically). If condition:
so, we would have a little less allocation comparing to master branch. |
Two primary goals:
I also think of another alternative: |
If we go to this direction, we can do in Options file: /// <summary>
/// Gets or sets a Subscribe function.
/// </summary>
public Action<object> Subscribe { get; set; } With that, we can simply subscribe for events like this: using (Sdk.CreateTracerProviderBuilder()
.AddHttpClientInstrumentation(options =>
{
options.Subscribe = (req) => { Console.WriteLine(req); };
})
.AddProcessor(activityProcessor.Object)
.Build())
{
using var c = new HttpClient();
using var r = await c.GetAsync("https://opentelemetry.io/").ConfigureAwait(false);
} For the instrumentation itself: if (activity.IsAllDataRequested)
{
this.options?.Subscribe(request);
activity.SetTag(SemanticConventions.AttributeHttpMethod, HttpTagHelper.GetNameForHttpMethod(request.Method));
activity.SetTag(SemanticConventions.AttributeHttpHost, HttpTagHelper.GetHostTagValueFromRequestUri(request.RequestUri));
activity.SetTag(SemanticConventions.AttributeHttpUrl, request.RequestUri.OriginalString);
if (this.options.SetHttpFlavor)
{
activity.SetTag(SemanticConventions.AttributeHttpFlavor, HttpTagHelper.GetFlavorTagValueFromProtocolVersion(request.Version));
}
} With that, the customer would have access to those properties, and we wouldn't have any issue with performance / allocation. Below, the benchmark:
I can see that Mean/Error/StdDev increased. But, allocated decreased. What do you think? @cijothomas @reyang @CodeBlanch |
@eddynaka Regarding this API: public Action<object> Subscribe { get; set; } I've started to build this API before and ended up backing off because there's a surprising amount of things to deal with. You have 3 things you might want to pull data from: request, response, & (potentially) an exception. Should we have an enrichment function for all three things? That seems like a lot. But if you combine it all, it starts to get a little messy: public Action<Activity, HttpContext, Exception> ActivityEnrichmentAction { get; set; } (That is passing HttpContext to combine at least request & response.) Just some things to consider 😄 |
Hi @CodeBlanch , that's why I added as And the user would have to typeof/gettype to see what is it to do something. What is a surprise for me is the increase of time. |
I'll look at it a bit more closely later to try and understand the performance. Giving user an |
Hi @CodeBlanch , did you have time to take a look? Should I go and maintain the if / Action / other? Let me know! :) |
@eddynaka Hey I'm kind of confused where are on this. I don't see the Right now I see:
That doesn't make a whole lot of sense to me. I think if we have the flag, we should store using activity.SetCustomProperty. My idea to use RuntimeContext was basically a way to get around needing the flag. It was originally to move the flag to RuntimeContext and use the activity for the storage. Maybe we should do a call on this to get everyone on the same page with the design? |
Hum.. i think I didn't push it. What I will do in this branch:
|
Below a simple benchmark:
If we go with flag: the activity gets big |
@eddynaka Apologies for not looking at this sooner! @cijothomas To answer the fundamental question, how expensive is [Benchmark]
public void BasicActivity()
{
using Activity activity = new Activity("Activity");
}
[Benchmark]
public void ActivityWithSingleCustomProperty()
{
using Activity activity = new Activity("Activity");
activity.SetCustomProperty("activity", activity);
}
[Benchmark]
public void ActivityWithManyCustomProperties()
{
using Activity activity = new Activity("Activity");
activity.SetCustomProperty("activity1", activity);
activity.SetCustomProperty("activity2", activity);
activity.SetCustomProperty("activity3", activity);
} Results:
The results make sense to me. The first one you add, big hit. It has to create the storage. Anything added after that, negligible. Right now we add the Resource on all sampled non-suppressed Activity objects: That means we are already paying the big price, regardless of what the instrumentation does. Unless we change that, all of this work is moot. But I was chatting with @eddynaka on Gitter earlier and his goal is to fix that 😄 Right now we have 3 options on this PR:
None of them are absolutely perfect. 1 & 2 allow you to plug in an ActivityProcessor to enrich based on the raw sources. Given the spec says ActivityProcessor is our extension point for enrichment, it feels to me like we should support that. But Option 3 is probably more useful for most users to quickly enrich without having to build an ActivityProcessor. I'm thinking we scope this PR to Option 3 and then maybe we can do something to enable the other case? That being said, I really don't like the API...
User will have to do expensive checks to figure out if object is request, response, or exception. They also need to access Activity.Current. I think at the minimum it should be...
Where the string is the event name. That would help with knowing what object is going to be. I would prefer something strongly typed but that also comes with some challenges. |
updating tests updating to RuntimeContextSlot updating to runtimeContext adding runtime/subscibe/flag benchmark updating aspnet and http updating ramaining instrumentation
src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs
Outdated
Show resolved
Hide resolved
This PR looks good to me, assuming its implement option 3 @CodeBlanch mentioned. We can discuss alternate namings, comments, example usage in a subsequent PR. |
src/OpenTelemetry.Instrumentation.AspNet/AspNetInstrumentationOptions.cs
Show resolved
Hide resolved
@@ -112,7 +112,7 @@ public override void OnStartActivity(Activity activity, object payload) | |||
|
|||
if (activity.IsAllDataRequested) | |||
{ | |||
activity.SetCustomProperty(RequestCustomPropertyName, request); | |||
this.options.Enrich?.Invoke(activity, "OnStartActivity", request); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also - can you ensure that unit test validate that Enrich is called only if Activity is sampled-in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just added a test. Let me know what do you think before I update everything
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Refs #1206
Changes
Activity.CustomProperty. To enrich activity, use the Enrich action on the
instrumentation.
Please provide a brief description of the changes here.
For significant contributions please make sure you have completed the following items:
CHANGELOG.md
updated for non-trivial changes