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

Make GetSpan return a statically allocated invalid span #3037

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

michaelvanstraten
Copy link

I’ve observed that the current implementation of GetSpan performs a new allocation each time it is called if no active span is found. Since DefaultSpan is immutable (or at least intended to be), it seems reasonable to return a statically allocated invalid span instead.

This approach could reduce unnecessary heap allocations and improve performance. However, it also introduces the possibility of someone inadvertently replacing the default invalid span with a valid one.

I’m not certain whether this change aligns with the intended design or if the allocation was deliberately chosen to provide more isolation for the default span. An alternative could be to initialize an invalid span at the start of the program if the goal is to eliminate the allocation overhead in production. Nonetheless, there might be other implications to consider.

Please consider this PR as a suggestion and an opportunity for discussion.

Copy link

linux-foundation-easycla bot commented Aug 19, 2024

CLA Signed

The committers listed above are authorized under a signed CLA.

  • ✅ login: michaelvanstraten / name: Michael (7f42908)
  • ✅ login: lalitb / name: Lalit Kumar Bhasin (ebb0bca)

@michaelvanstraten
Copy link
Author

michaelvanstraten commented Aug 19, 2024

By initializing the invalid span at process start time, I mean setting it up like this:

void main() {
    std::shared_ptr<Span> invalid_span =
      std::make_shared<DefaultSpan>(SpanContext::GetInvalid());
    opentelemetry::trace::SetSpan(invalid_span);
    // ...
}

Copy link

codecov bot commented Aug 19, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 87.63%. Comparing base (497eaf4) to head (ebb0bca).
Report is 121 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3037      +/-   ##
==========================================
+ Coverage   87.12%   87.63%   +0.52%     
==========================================
  Files         200      190      -10     
  Lines        6109     5869     -240     
==========================================
- Hits         5322     5143     -179     
+ Misses        787      726      -61     
Files Coverage Δ
api/include/opentelemetry/trace/context.h 92.31% <100.00%> (+0.65%) ⬆️

... and 124 files with indirect coverage changes

@lalitb
Copy link
Member

lalitb commented Aug 19, 2024

Just curious, where are you getting issue in usage of GetSpan?

  • One place it is used internally is during creation of new span in Tracer:StartSpan(), and usually object of type SpanContext (and not Context) is passed as the options.parent argument. This eliminates the call to GetSpan internally.
  • Another place it is used is in the TextMapPropagator::Inject() implementation. The application can maintain a static invalid span, and pass it to Inject method whenever the current context is invalid.

Just couple of thoughts came to my mind as the potential solution/workarounds to avoid any changes in GetSpan.

@michaelvanstraten
Copy link
Author

Just curious, where are you getting issue in usage of GetSpan?

I wouldn't necessarily call it an "issue," but it seems suboptimal for GetSpan to allocate a new span each time a default span is needed. Ideally, it would return a statically allocated span when no active span is found in the current context.

Are there any potential downsides to returning a statically allocated span as the default?

I'm currently working on integrating OpenTelemetry into parts of Gecko (Firefox), and depending on the level of instrumentation, this could potentially impact performance.

@lalitb
Copy link
Member

lalitb commented Aug 26, 2024

Are there any potential downsides to returning a statically allocated span as the default?

I believe not. It should be fine to return static allocation for an invalid span. I just don't understand how can the flow possibly reach there. Feel free to make it ready for review.

@michaelvanstraten michaelvanstraten marked this pull request as ready for review August 27, 2024 14:32
@michaelvanstraten michaelvanstraten requested a review from a team August 27, 2024 14:32
@michaelvanstraten
Copy link
Author

michaelvanstraten commented Aug 27, 2024

I just don't understand how can the flow possibly reach there. Feel free to make it ready for review.

If you don't have a root span in main, then this will happen (since we primarily use contexts to control when tracing is active).

void main() {
    for (int i = 0; i < 100,000; i++) {
        foo(); // Will result in 100,000 allocations
    }

    auto provider = opentelemetry::trace::Provider::GetTracerProvider();
    auto tracer = provider->GetTracer("foo_library", "1.0.0");
    
    auto span = tracer->StartSpan("foo");
    auto scope = tracer->WithActiveSpan(span);
    
    for (int i = 0; i < 100,000; i++) {
        foo(); // Will result in 0 allocations
    }
};

void foo() {
    auto active_span = opentelemetry::trace::GetSpan(
        opentelemetry::context::RuntimeContext::GetCurrent());
    active_span.AddEvent("something in foo");
}

@michaelvanstraten michaelvanstraten marked this pull request as draft August 28, 2024 16:22
@michaelvanstraten michaelvanstraten marked this pull request as ready for review August 31, 2024 21:45
@michaelvanstraten
Copy link
Author

Sorry for the confusion, my college mentioned some potential raise conditions which turned out to be not applicable here.

@marcalff
Copy link
Member

marcalff commented Sep 4, 2024

Sorry for the confusion, my college mentioned some potential raise conditions which turned out to be not applicable here.

Just to clarify, should we continue with this PR, or should it be closed (because of the race found) ?

As a side note, if the main concern is performances, the best course of action could be to avoid reaching this case in the first place (i.e., provide a root span in main), instead of optimizing non nominal use cases.

@michaelvanstraten
Copy link
Author

Yes, I've marked the PR as a draft for the time being where the race was unclear, but it does not apply here. So please continue.

The reason for this PR is that for users not familiar with the lib this might not be obvious.

If this change is too invasive, we can close this PR and add a note in the docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants