[PROF-13510] Heap profiling for ruby 4.x#5201
Conversation
|
Thank you for updating Change log entry section 👏 Visited at: 2026-01-29 11:52:43 UTC |
BenchmarksBenchmark execution time: 2026-02-05 15:29:12 Comparing candidate commit e02752c in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 44 metrics, 2 unstable metrics. |
c4fdb76 to
49477bf
Compare
|
Reminder that to measure this, we should do ON/OFF. Some of the cost will be in the VM itself. |
|
✅ Tests 🎉 All green!❄️ No new flaky tests detected 🎯 Code Coverage 🔗 Commit SHA: e02752c | Docs | Datadog PR Page | Was this helpful? Give us feedback! |
ivoanjo
left a comment
There was a problem hiding this comment.
I ran out of time today, here's what I got so far! In particular, I still need to stare at heap_recorder.c for longer, I'm not yet convinced it's correct, I'm seeing some state getting carried across calls that I'm not confident is right.
The current notes are small-ish stuff, other than the extra overhead that's unneeded (and I believe should be easy to fix) + the code exposing the heap recorder directly to the cpu and wall collector that ideally I'd like to avoid too if possible.
ivoanjo
left a comment
There was a problem hiding this comment.
Ok here's my comments from the full pass :)
I think the key change needed is to make sure we call during_sample_enter to avoid any other parts of the profiler firing in the middle of finalization.
Having said that, especially on the heap profiler I'm not a huge fan of some of the duplication -- that code is ultra-fiddly and so having complex logic duplicated across if-defs I worry makes it easy to forget to update one of the versions.
e966c43 to
ea9eba7
Compare
|
I took into account major comments. |
ea9eba7 to
4dbdd06
Compare
The idea is to delay the time at which we record object ids. Once we are outside of the allocation code path, we can request the object ID.
- Avoid scheduling many postponed jobs This fixes some of the issues we had with accuracy Also I suspect that this has less overhead - Avoid re-entrancy based on Ivo's comments
Although some of this code is dead code on legacy Rubies, always compiling it in means less ifdefs spread throughout and it helps keep the code focused on modern rubies, rather than on legacy ones.
This check is already covered by `heap_recorder->active_recording != NULL` (they're set and unset together).
In the future we may end up using the deferred recording for legacy rubies as well, so might as well lay the groundwork.
**What does this PR do?** This PR adds Ruby 4 heap profiling test variants. As per DataDog/dd-trace-rb#5201, we need a different implementation of heap profiling on Ruby 4, so we want to validate it's still sane with prof-correctness. These variants are effectively the same as the regular ones but: * Replace Ruby 3.3 with Ruby 4.0 * Point to the branch from DataDog/dd-trace-rb#5201 (we'll need to change this back to master once landed) * Set the `DD_PROFILING_EXPERIMENTAL_HEAP_RUBY4_ENABLED` env variable we're using to gate the new feature * Update the `expected_profile.json` to take into account that object allocation in Ruby 4 produces a slightly different stack trace (there's no `new` method on the stack -- it gets inlined into the caller) **Motivation:** Validate Ruby 4 heap profiling. **Additional Notes:** There's still a few anomalies in the results we're looking into... **How to test the change?** Run as usual!
**What does this PR do?** This PR adds Ruby 4 heap profiling test variants. As per DataDog/dd-trace-rb#5201, we need a different implementation of heap profiling on Ruby 4, so we want to validate it's still sane with prof-correctness. These variants are effectively the same as the regular ones but: * Replace Ruby 3.3 with Ruby 4.0 * Point to the branch from DataDog/dd-trace-rb#5201 (we'll need to change this back to master once landed) * Set the `DD_PROFILING_EXPERIMENTAL_HEAP_RUBY4_ENABLED` env variable we're using to gate the new feature * Update the `expected_profile.json` to take into account that object allocation in Ruby 4 produces a slightly different stack trace (there's no `new` method on the stack -- it gets inlined into the caller) **Motivation:** Validate Ruby 4 heap profiling. **Additional Notes:** There's still a few anomalies in the results we're looking into... **How to test the change?** Run as usual!
Allow heap profiling to be enabled on Ruby 4 without any extra flags.
|
TODO: We're still investigating a few benchmarks / validation results, but this should be ready for review. |
AlexJF
left a comment
There was a problem hiding this comment.
Looks mostly good to me. Some minor feedback to try to prevent so much split logic.
Also, if the deferred mechanism is also compatible with Ruby < 4, could it make sense to unify codepaths and have all be deferred? Or does this incur a perf penalty?
r1viollet
left a comment
There was a problem hiding this comment.
LGTM (can't approve as I opened this)
Though feel free to merge.
I was thinking that a test that would exercise some of the raise code paths could be good, though not blocking for this PR.
Performance looks the same as other heap instrumentation.
Accuracy tests are OK.
`end_heap_allocation_recording` should not be called directly, we always go through `end_heap_allocation_recording_with_rb_protect` and thus observing `SKIPPED_RECORD` inside `end_heap_allocation_recording` is a bug.
|
I think this is good shape! Pressing the nice green button! |

What does this PR do?
This PR reimplements part of the heap profiler to support Ruby 4 (which was
disabled in #5148 ).
Motivation:
Support heap profiling on Ruby 4!
Change log entry
Yes. Add support for heap profiling on Ruby 4.
For the highlights:
Additional Notes:
The Datadog Ruby heap profiler tracks live heap objects by storing their
object_idwhen they're allocated, then later usingObjectSpace._id2refto check if those objects are still alive. This mechanism is currently incompatible with Ruby 4.x.Key Components
collectors_cpu_and_wall_time_worker.c- Main sampling coordinatorheap_recorder.c- Tracks live heap objects using object IDsAllocation Flow (Before Fix)
Liveness Check Flow
The Ruby 4.x Problem
What Changed
Ruby 4.x changed how
object_idworks internally. The key issue:on_newobj_eventis called during object allocation (object is in "in-between state")rb_obj_id()during this event mutates the object (assigns an ID)Implemented Solution: Deferred Object ID Recording
We defer the
rb_obj_id()call to after the allocation tracepoint completes usingrb_postponed_job_trigger.Allocation Flow (After Fix - Ruby 4+)
How to test the change?
The usual test coverage uses the new code path on Ruby 4.
We've also added additional coverage in DataDog/prof-correctness#89 + internally in the reliability environment.