-
Notifications
You must be signed in to change notification settings - Fork 4k
ARROW-15067: [C++] Add tracing spans to the scanner #11964
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
Conversation
|
|
|
TODO:
Example of output: Details |
|
CC @westonpace. You will need ARROW-15044/#11925 to actually see the spans (or a local OTLP collector instance). I will circle back and try to isolate the issue with ASan later. That said, I think impact is minimal; it shouldn't affect CI and we can disable ASan or use the suppressions for local development, and it also wouldn't be enabled for things like Conbench. I haven't quantified the performance impact here, either (do we have a benchmark that would stress these paths? I can run locally and report) |
7093bc2 to
c086ab8
Compare
|
Rebased with ARROW-15044. |
westonpace
left a comment
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.
Looks very useful. Just a few (potentially naive) thoughts.
cpp/src/arrow/dataset/file_csv.cc
Outdated
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.
Could we push this ifdef into StartSpan by returning a dummy span object with no-op methods?
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.
We could. I didn't want to wrap too much of the API, also, I figured this would be best if people were very concerned about overhead.
c086ab8 to
d73e4ad
Compare
| template <typename T> | ||
| AsyncGenerator<T> PropagateSpanThroughAsyncGenerator( | ||
| AsyncGenerator<T> wrapped, | ||
| opentelemetry::nostd::shared_ptr<opentelemetry::trace::Span> span) { | ||
| return [=]() mutable -> Future<T> { | ||
| auto scope = GetTracer()->WithActiveSpan(span); | ||
| return wrapped(); | ||
| }; | ||
| } |
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.
On second glance this helper method seems a little off to me. Is the "active span" a thread local concept? Will this work even if wrapped() launches its task on a separate thread (e.g. an I/O operation)?
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.
Yes, active span is thread local. wrapped() must manually propagate the active span (or manually pass the span through) if it itself spawns a thread. That is a disadvantage and it does make it hard to use OpenTelemetry while making it possible to completely remove it at compile time.
One way to get around this would be to instrument the Executor and possibly Future classes themselves, but I worry this would have more overhead than is desirable. (Or maybe not. I haven't tried.)
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.
Concretely I'm thinking of...
#ifdef ARROW_WITH_OPENTELEMETRY
batch_gen_gen = arrow::internal::tracing::PropagateSpanThroughAsyncGenerator(
std::move(batch_gen_gen));
#endif
If you are I/O bound then I would expect batch_gen_gen will be transferring to an I/O thread (and back) for every item. There are "async-local" concepts (e.g. https://docs.microsoft.com/en-us/dotnet/api/system.threading.asynclocal-1?view=net-6.0) so maybe we need to adopt something like that. I think that's the same thing as "instrumenting the executor and possibly future classes themselves". I think it would be fairly affordable (submitting a thread task would have to copy a handle to the active span or "async context" to include as part of the task and then the first thing in the task would be setting the active span based on that handle).
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.
Yes, I think that's essentially the same (OpenTelemetry maintains a context which is thread-local by default, I think it can even be swapped out depending on how we want to go about things?). I'll try to take a look at this approach when I get a chance.
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.
Should it be done in a follow-up? I think, at the moment, the consequence would be that spans don't have proper parentage but other than that it should be fairly harmless.
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.
Yeah, I think we can do that. If it pans out we can hopefully replace the manual instrumentation done here. I'll file a JIRA to explore this further.
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.
westonpace
left a comment
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.
Thanks for doing this. I'm still not really in love with the proliferation of ifdef but I understand the motivation and I think we can get a better sense for what that feels like later.
| auto tracer = arrow::internal::tracing::GetTracer(); | ||
| auto span = tracer->StartSpan("arrow::dataset::CsvFileFormat::OpenReaderAsync"); | ||
| #endif | ||
| ARROW_ASSIGN_OR_RAISE(auto reader_options, GetReadOptions(format, scan_options)); |
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.
Technically the GetReadOptions call could fail and it would bail out of the method without marking the span as finished. I'm not sure there is any easy and good solution though since we aren't using exceptions. Any ideas?
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.
The span will be marked as finished in its destructor by default. Explicit marking is only required when you want to control the end time: https://github.com/open-telemetry/opentelemetry-cpp/blob/f20f72f3a904b215fc750b67b206f158aeb61241/sdk/src/trace/span.cc#L89-L92
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.
Ah, that makes sense!
|
#12100 would help with the ifdef proliferation, if we want to build on that. |
|
Continued in #12328. |
Continuing #12328 and #11964. The tracing spans were not propagated through all the asynchronous constructs, causing some spans to become disconnected from the trace. This PR aims to address this. Some things left to do: - [x] Possibly add some attributes to the `read_column` span - [x] fix parent/sibling relationships (some of the new spans should probably become a child) - [x] Do something about all the `#ifdefs` - [x] Wrap around a `Future` - [x] Wrap `Executor` - [x] Check if tracing now works properly for all of the file types, not just parquet - [x] lidavidm mentioned some memory leaks that should be investigated - [x] The `FragmentToBatches` span seems to be active way too long Closes #12609 from joosthooz/arrow-15067 Lead-authored-by: Joost Hoozemans <joosthooz@msn.com> Co-authored-by: David Li <li.davidm96@gmail.com> Co-authored-by: Matthijs Brobbel <m1brobbel@gmail.com> Signed-off-by: David Li <li.davidm96@gmail.com>
This adds spans to the scanner, one per fragment and one per batch per fragment, that are enabled based on #ifdefs.