Skip to content

[PROF-13510] Heap profiling for ruby 4.x#5201

Merged
ivoanjo merged 39 commits into
masterfrom
r1viollet/heap-profiling-4.0
Feb 5, 2026
Merged

[PROF-13510] Heap profiling for ruby 4.x#5201
ivoanjo merged 39 commits into
masterfrom
r1viollet/heap-profiling-4.0

Conversation

@r1viollet

@r1viollet r1viollet commented Jan 6, 2026

Copy link
Copy Markdown
Contributor

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:

# Heap profiling can now be used on Ruby 4

We've now added support for heap profiling on Ruby 4.0 .
[Check the docs](https://docs.datadoghq.com/profiler/enabling/ruby/?tab=environmentvariables#configuration) for how to enable it.

Additional Notes:

The Datadog Ruby heap profiler tracks live heap objects by storing their object_id when they're allocated, then later using ObjectSpace._id2ref to check if those objects are still alive. This mechanism is currently incompatible with Ruby 4.x.

Key Components
  1. collectors_cpu_and_wall_time_worker.c - Main sampling coordinator
  2. heap_recorder.c - Tracks live heap objects using object IDs
Allocation Flow (Before Fix)
on_newobj_event()  →  start_heap_allocation_recording()  →  end_heap_allocation_recording()
                              ↓
                      rb_obj_id(new_obj)  ← PROBLEM HERE
Liveness Check Flow
heap_recorder_update()  →  st_object_record_update()  →  ruby_ref_from_id()
                                                              ↓
                                                      ObjectSpace._id2ref(obj_id)
The Ruby 4.x Problem
What Changed

Ruby 4.x changed how object_id works internally. The key issue:

  • on_newobj_event is called during object allocation (object is in "in-between state")
  • Calling rb_obj_id() during this event mutates the object (assigns an ID)
  • This mutation is not safe during the allocation tracepoint in Ruby 4.x
  • Reference: Ruby Issue #21710
Implemented Solution: Deferred Object ID Recording

We defer the rb_obj_id() call to after the allocation tracepoint completes using rb_postponed_job_trigger.

Allocation Flow (After Fix - Ruby 4+)
on_newobj_event()
    ↓
start_heap_allocation_recording()
    - Store VALUE in heap_recorder->active_deferred_object
    - Store metadata in heap_recorder->active_deferred_object_data
    ↓
end_heap_allocation_recording()
    - Move to pending_recordings[] array
    ↓
rb_postponed_job_trigger()
    ↓
finalize_heap_allocation_from_postponed_job()  ← Runs outside allocation tracepoint
    - heap_recorder_finalize_pending_recordings()
    - Call rb_obj_id() safely
    - Commit object_record to heap_record

⚠️ These changes are AI assisted and will require careful review & analysis of performance impacts.

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.

@github-actions github-actions Bot added the profiling Involves Datadog profiling label Jan 6, 2026
@github-actions

github-actions Bot commented Jan 6, 2026

Copy link
Copy Markdown

Thank you for updating Change log entry section 👏

Visited at: 2026-01-29 11:52:43 UTC

@pr-commenter

pr-commenter Bot commented Jan 6, 2026

Copy link
Copy Markdown

Benchmarks

Benchmark execution time: 2026-02-05 15:29:12

Comparing candidate commit e02752c in PR branch r1viollet/heap-profiling-4.0 with baseline commit 1ef92de in branch master.

Found 0 performance improvements and 0 performance regressions! Performance is the same for 44 metrics, 2 unstable metrics.

@r1viollet
r1viollet force-pushed the r1viollet/heap-profiling-4.0 branch from c4fdb76 to 49477bf Compare January 6, 2026 17:46
@r1viollet

Copy link
Copy Markdown
Contributor Author

Reminder that to measure this, we should do ON/OFF. Some of the cost will be in the VM itself.

@datadog-official

datadog-official Bot commented Jan 9, 2026

Copy link
Copy Markdown

✅ Tests

🎉 All green!

❄️ No new flaky tests detected
🧪 All tests passed

🎯 Code Coverage
Patch Coverage: 87.10%
Overall Coverage: 95.15% (-0.01%)

View detailed report

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: e02752c | Docs | Datadog PR Page | Was this helpful? Give us feedback!

@ivoanjo ivoanjo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Comment thread ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c Outdated
Comment thread ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c Outdated
Comment thread ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c Outdated
Comment thread ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c Outdated
Comment thread ext/datadog_profiling_native_extension/extconf.rb Outdated
Comment thread spec/datadog/profiling/component_spec.rb Outdated
Comment thread ext/datadog_profiling_native_extension/heap_recorder.h Outdated
Comment thread ext/datadog_profiling_native_extension/heap_recorder.c Outdated
Comment thread ext/datadog_profiling_native_extension/heap_recorder.c Outdated
Comment thread ext/datadog_profiling_native_extension/heap_recorder.c Outdated

@ivoanjo ivoanjo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Comment thread ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c Outdated
Comment thread ext/datadog_profiling_native_extension/heap_recorder.c Outdated
Comment thread ext/datadog_profiling_native_extension/heap_recorder.c
Comment thread ext/datadog_profiling_native_extension/heap_recorder.c Outdated
Comment thread ext/datadog_profiling_native_extension/heap_recorder.c Outdated
Comment thread ext/datadog_profiling_native_extension/heap_recorder.c Outdated
Comment thread ext/datadog_profiling_native_extension/heap_recorder.c
Comment thread spec/datadog/profiling/stack_recorder_spec.rb Outdated
@r1viollet
r1viollet force-pushed the r1viollet/heap-profiling-4.0 branch 2 times, most recently from e966c43 to ea9eba7 Compare January 16, 2026 15:50
@github-actions github-actions Bot added the core Involves Datadog core libraries label Jan 16, 2026
@r1viollet

Copy link
Copy Markdown
Contributor Author

I took into account major comments.
Next step perf tests.
Then we can do style fixups.

@r1viollet
r1viollet force-pushed the r1viollet/heap-profiling-4.0 branch from ea9eba7 to 4dbdd06 Compare January 19, 2026 08:32
Comment thread spec/datadog/profiling/stack_recorder_spec.rb
@ivoanjo ivoanjo changed the title Heap profiling for ruby 4.x - Prototype [PROF-13510] Heap profiling for ruby 4.x - Prototype Jan 20, 2026
r1viollet and others added 15 commits January 21, 2026 14:28
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).
@ivoanjo

ivoanjo commented Jan 23, 2026

Copy link
Copy Markdown
Member

Quick side-by-side results from Ruby 3.3 vs 4.0 in our Ruby on Rails test app:

image

ivoanjo added a commit to DataDog/prof-correctness that referenced this pull request Jan 29, 2026
**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!
ivoanjo added a commit to DataDog/prof-correctness that referenced this pull request Jan 29, 2026
**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.
@ivoanjo ivoanjo changed the title [PROF-13510] Heap profiling for ruby 4.x - Prototype [PROF-13510] Heap profiling for ruby 4.x Jan 29, 2026
@ivoanjo

ivoanjo commented Jan 29, 2026

Copy link
Copy Markdown
Member

TODO: We're still investigating a few benchmarks / validation results, but this should be ready for review.

@ivoanjo
ivoanjo marked this pull request as ready for review January 29, 2026 11:53
@ivoanjo
ivoanjo requested review from a team as code owners January 29, 2026 11:53
@ivoanjo
ivoanjo requested a review from AlexJF January 29, 2026 11:53

@AlexJF AlexJF left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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?

Comment thread ext/datadog_profiling_native_extension/heap_recorder.c Outdated
Comment thread ext/datadog_profiling_native_extension/heap_recorder.c
Comment thread ext/datadog_profiling_native_extension/heap_recorder.c

@r1viollet r1viollet left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.
@ivoanjo

ivoanjo commented Feb 5, 2026

Copy link
Copy Markdown
Member

I think this is good shape! Pressing the nice green button!

@ivoanjo
ivoanjo merged commit 7631952 into master Feb 5, 2026
762 of 764 checks passed
@ivoanjo
ivoanjo deleted the r1viollet/heap-profiling-4.0 branch February 5, 2026 15:52
@github-actions github-actions Bot added this to the 2.29.0 milestone Feb 5, 2026
@y9v y9v mentioned this pull request Feb 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core Involves Datadog core libraries profiling Involves Datadog profiling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants