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

[PROF-7440] Use invoke location as a fallback for nameless threads in the profiler #2950

Merged
merged 6 commits into from
Jul 7, 2023

Conversation

ivoanjo
Copy link
Member

@ivoanjo ivoanjo commented Jul 5, 2023

What does this PR do?:

Naming Ruby threads is optional, and a lot of Ruby code still creates threads and doesn't name them.

As a workaround for Ruby threads that have no name, this PR makes the profiler use the "thread invoke location", which is what Ruby shows in the to_s/inspect of a thread, for instance:

[1] pry(main)> Thread.list.last.name
=> nil
[2] pry(main)> Thread.list.last.to_s
=> "#<Thread:0x00005934cc0143e8 thread-name.rb:3 sleep>"

The "invoke location" is the file and line where the block that starts the thread is defined (thread-name.rb:3 for the example above).

Motivation:

Having nameless threads is annoying because we show this information in several places of our UX, and with the timeline feature this will be even more visible.

Additional Notes:

Since Ruby doesn't have an API to get this information (other than calling to_s and then parsing its output), and because we want to avoid allocating any Ruby objects while the profiler is taking a sample, I chose to add to private_vm_api_access.c an API to get this information that is basically a heavily simplified version of Thread#to_s.

How to test the change?:

This change includes code coverage. Also, profile any app that starts and doesn't name threads, and you'll start seeing threads named "some_file.rb:some_line" in the profiler.

The `thread_invoke_location` can be used as a fallback alternative for
threads that have no name, and is what Ruby shows in `Thread#to_s` as
well.

This commit only gathers the invoke location and stores it in the
context; this information is not yet propagated in the pprof, that
will come in a later commit.
… the profiler

**What does this PR do?**:

Naming Ruby threads is optional, and a lot of Ruby code still creates
threads and doesn't name them.

As a workaround for Ruby threads that have no name, this PR makes the
profiler use the "thread invoke location", which is what Ruby shows in
the `to_s`/`inspect` of a thread, for instance:

```
[1] pry(main)> Thread.list.last.name
=> nil
[2] pry(main)> Thread.list.last.to_s
=> "#<Thread:0x00005934cc0143e8 thread-name.rb:3 sleep>"
```

The "invoke location" is the file and line where the block that
starts the thread is defined.

**Motivation**:

Having nameless threads is annoying because we show this information
in several places of our UX, and with the timeline feature this will be
even more visible.

**Additional Notes**:

Since Ruby doesn't have an API to get this information (other than
calling `to_s` and then parsing its output), and because we want to
avoid allocating any Ruby objects while the profiler is taking a sample,
I chose to add to `private_vm_api_access.c` an API to get this
information that is basically a heavily simplified version of
`Thread#to_s`.

**How to test the change?**:

This change includes code coverage. Also, profile any app that starts
and doesn't name threads, and you'll start seeing threads named
"some_file.rb:some_line" in the profiler.
@ivoanjo ivoanjo requested a review from a team July 5, 2023 16:23
@github-actions github-actions bot added the profiling Involves Datadog profiling label Jul 5, 2023
#ifndef NO_THREAD_INVOKE_ARG // Ruby 2.6+
if (thread->invoke_type != thread_invoke_type_proc) return NULL;

VALUE proc = thread->invoke_arg.proc.proc;
Copy link
Member

Choose a reason for hiding this comment

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

proc proc proc

Copy link
Member Author

Choose a reason for hiding this comment

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

proc proc? prooooc!

@ivoanjo ivoanjo merged commit 4890f92 into master Jul 7, 2023
@ivoanjo ivoanjo deleted the ivoanjo/prof-7440-improve-thread-naming branch July 7, 2023 07:48
@github-actions github-actions bot added this to the 1.13.0 milestone Jul 7, 2023
ivoanjo added a commit that referenced this pull request Jul 24, 2023
…rted in native code

**What does this PR do?**:

This PR adds a fallback invoke location (which gets used as a name) for
threads that a) Are unnamed, and b) Were started from native code.

The added invoke location is the fixed string
`(Unnamed thread from native gem)`.

**Motivation**:

We previously (in #2950) started using invoke location for threads
that do not have a name BUT when a thread is started from native
code it would have an empty name.

Having an empty name is not particularly helpful, so I decided to
instead have a simple placeholder that at least provides an hint on
what the thread may be.

**Additional Notes**:

We don't yet do any resolving of symbols from native code, but in
the future I guess we may try to see if the native code has a name
at the C level, and use that as a fallback, but that's much more
than a one-liner change :)

**How to test the change?**:

Change includes code coverage.
ivoanjo added a commit that referenced this pull request Oct 4, 2023
… gem is in use

**What does this PR do?**

In #2950, we added a fallback for nameless threads: we use their
"invoke location", as shown by Ruby in the default `Thread#to_s`.
The invoke location is the file and line of the first method of the
thread.

This fallback has an issue: when thread creation is monkey patched.
One common source of this is the `logging` gem. When the `logging`
gem monkey patches thread creation, every thread will have the
same invoke location, which will point to the `logging` gem.

This made the fallback invoke location worse than not having anything.

To work around this, this PR changes the profiler so that when the
invoke location belongs to the `logging` gem, it's not used, and
a simpler `(Unnamed thread)` placeholder is used instead.

**Motivation:**

This issue came up when testing the timeline feature with some of
the internal Ruby apps.

**Additional Notes:**

In the future we could probably explore a more generic fix
(e.g. using `Thread.method(:new).source_location` or something
like that to detect redefinition) but doing that from the profiler
native code is a bit more work so I decided to go with a simpler
approach.

**How to test the change?**

Change includes test coverage. You can also see the thread name
difference on an app by creating a simple thread before/after loading
the `logging` gem and see the fallback name for the thread.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
profiling Involves Datadog profiling
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants