-
Notifications
You must be signed in to change notification settings - Fork 765
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
[Performance] added SpanAttributes.Add(string,object)
method for deferred string
-ification
#4672
[Performance] added SpanAttributes.Add(string,object)
method for deferred string
-ification
#4672
Conversation
This is a performance fix aimed at making it possible to defer `string`-ification of complex types until the `Activity` is being processed by the OTel pipeline. This type of performance work is possible when working directly on `System.Diagnostics.ActivityTagsCollection`, but not when working with `OpenTelemetry.Api.Trace.SpanAttributes`.
Not sure if this needs a |
Codecov Report
Additional details and impacted files@@ Coverage Diff @@
## main #4672 +/- ##
==========================================
- Coverage 85.08% 85.05% -0.03%
==========================================
Files 314 314
Lines 12725 12727 +2
==========================================
- Hits 10827 10825 -2
- Misses 1898 1902 +4
|
/// </summary> | ||
/// <param name="key">Entry key.</param> | ||
/// <param name="value">Entry value.</param> | ||
public void Add(string key, object value) |
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.
what is the use case for this? Though Activity allow object
type, OTel only allows a specific set of types here, which is already supported in this class.
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.
In this case, it was to be able to have an object rendered into a string out of band with the hot path being traced. I'm on mobile now so I can't provide a code sample, but you can already accomplish this by passing an IEnumerable of KV<string,object> into the constructor.
Exposing this method eliminates the need to do that and writes to the underlying collection.
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.
Having looked at the spec, it doesn't preclude SDKs from having this method, just that the others MUST exist. I don't see a reason that the .NET SDK can't have extra stuff that isn't in the base spec?
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.
I don't think we should be adding this overload for the following reasons:
- You don't need a new API to defer the
ToString()
call. You should be able to use the existingSpanAttributes
ctor that takes in anIEnumerable<KeyValuePair<string, object>>
to directly pass in a complex type. Could you talk more about why this option wouldn't work for you? - Just because .NET's
Activity
API deviates from the spec by allowing an arbitrary object as an attribute value, doesn't mean we should give up on keeping theSpan
and related APIs conform to the spec. - What's the amount of perf improvement expected by this? Does the magnitude of perf improvement justify adding the new public API?
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.
If I were to base my opinion solely on the spec, I do not think we should add this overload to Add
.
While the motivation for this PR is performance, my reason for approving it is not.
I approve for two reasons:
- API consistency
- As @Aaronontheweb highlights, I agree that since the constructor accepts a collection of
KeyValuePair<string, object>
s, it is awkward that there is noAdd(string, object)
method.
- As @Aaronontheweb highlights, I agree that since the constructor accepts a collection of
- Historical decisions we have made with regards to handling span attributes
- The Activity API allows for arbitrary
object
valued attributes. - We made the decision awhile ago that SDK components would export arbitrary
object
valued attributes, transforming them as necessary to a spec supported type (e.g., to a string).
- The Activity API allows for arbitrary
However, with regards to performance, we discussed in today's meeting wanting to see some benchmarks. I agree benchmarks could be useful, particularly for any other users of the shim API.
Since multiple comments have asked for benchmarks. While working on our commercial library built on top of the OTEL API we conducted extensive performance testing, and "deferred stringification" was the most significant performance impact we made. Full post is here: https://petabridge.com/blog/phobos-2.4-opentelemetry-optimizations/ Original CodeSimplified this some, but this is what our span creation looks like around the hot path private TelemetrySpan CreateActiveSpan(object underlyingMsg, SpanContext? context = null,
Baggage baggage = default, bool followFromActiveSpan = true, long? startTimeUtcTicks = null)
{
var startTime = startTimeUtcTicks != null
? new DateTimeOffset(startTimeUtcTicks.Value, TimeSpan.Zero)
: DateTimeOffset.UtcNow;
TelemetrySpan sp = null;
if (context == null)
{
sp = Tracer.StartRootSpan(underlyingMsg.GetType().GetOperationName(),
SpanKind.Consumer, InitialAttributes, startTime: startTime);
Tracer.WithSpan(sp); // mark the current span as active
}
else
{
sp = Tracer.StartActiveSpan(underlyingMsg.GetType().GetOperationName(),
SpanKind.Consumer, parentContext: context.Value, InitialAttributes, startTime: startTime);
}
if (startTimeUtcTicks != null)
{
sp.AddEvent("waiting", startTime);
}
var attributes = new SpanAttributes();
attributes.Add("content", underlyingMsg.ToString());
sp.AddEvent("message", attributes);
return sp;
} Original benchmark values
As you can see - both the memory allocation in the hot path AND the end to end latency changes with the type of message being processed:
New codeThis code uses the existing public OTEL APIs - I allocate an additional // rest of CreateActiveSpan method
static IEnumerable<KeyValuePair<string, object>> CreateSpanAttributes(object msg)
{
yield return new KeyValuePair<string, object>("content", msg);
}
var attributes = new SpanAttributes(CreateSpanAttributes(underlyingMsg));
sp.AddEvent("message", attributes);
return sp; Updated benchmark values
Default memory allocations go up for the primitive case, but otherwise the hot-path's memory consumption and latency is now consistent regardless of the input data type - this is because the expensive operation of rendering the string now happens out-of-band inside the OTEL processing / export pipeline instead. If you'd like me to add a version of this benchmark to this PR, I'd be happy to do it - but these are our numbers demonstrating that the techique of deferring |
We would ideally like to see two minimal benchmarks (code and results) which ONLY target the creation of
This should be a one-time allocation. You don't need to allocate this repeatedly on the hot-path. For your example, you could probably get better results by creating an array or a list and reusing it. private KeyValuePair<string, object>[] initialAttributes = new KeyValuePair<string, object>[1] { new("content", "TBD") };
private TelemetrySpan CreateActiveSpan(object underlyingMsg, SpanContext? context = null,
Baggage baggage = default, bool followFromActiveSpan = true, long? startTimeUtcTicks = null)
{
// rest of CreateActiveSpan method
initialAttributes[0] = new KeyValuePair<string, object>("content", underlyingMsg);
var attributes = new SpanAttributes(initialAttributes);
attributes.Add("content", underlyingMsg.ToString());
sp.AddEvent("message", attributes);
return sp;
}
Could you elaborate more on these scenarios which cannot be achieved using the existing APIs? |
No, has to be allocated every time as this value is dynamic per-call Edit: Ah, I see what you mean - I'll check that out. I'll add a benchmark |
This PR was marked stale due to lack of activity and will be closed in 7 days. Commenting or Pushing will instruct the bot to automatically remove the label. This bot runs once per day. |
Closed as inactive. Feel free to reopen if this PR is still being worked on. |
Changes
This is a performance fix aimed at making it possible to defer
string
-ification of complex types until theActivity
is being processed by the OTel pipeline.This type of performance work is possible when working directly on
System.Diagnostics.ActivityTagsCollection
, but not when working withOpenTelemetry.Api.Trace.SpanAttributes
.Merge requirement checklist
CHANGELOG.md
files updated for non-trivial changes