Skip to content

perf(profiling)!: use Function2 and Mapping2 from ProfilesDictionary#1594

Draft
morrisonlevi wants to merge 9 commits intolevi/api2-eq-hashfrom
levi/profiling-remove-legacy-ids
Draft

perf(profiling)!: use Function2 and Mapping2 from ProfilesDictionary#1594
morrisonlevi wants to merge 9 commits intolevi/api2-eq-hashfrom
levi/profiling-remove-legacy-ids

Conversation

@morrisonlevi
Copy link
Contributor

@morrisonlevi morrisonlevi commented Feb 18, 2026

What does this PR do?

  • Uses Function2 and Mapping2 from ProfilesDictionary as the core Function/Mapping representations.
  • Removes other representations. This is a breaking change, currently the diff is +635 -2,246, which gives you an idea of how much is relatively removed.

Motivation

Performance and simpler data flow.

On main, we translate functions and mappings from the long-lived dictionary into the short-lived Profile internals. This translation mainly exists for backward compatibility. By dropping that compatibility path, we can remove the extra conversion work and improve performance.

Here's the benchmark command:

cargo bench -p libdd-profiling --bench main -- \
  profile_add_sample2_frames_x1000 \
  --warm-up-time 1 \
  --measurement-time 5 \
  --sample-size 20

And the results:

Reference Time (lower/median/upper) Criterion change
main [302.93 µs 306.01 µs 310.63 µs] N/A (baseline)
levi/profiling-remove-legacy-ids [237.07 µs 241.90 µs 247.83 µs] -22.426% to -20.044%

In other words, adding samples using the try_add_sample2 API is now faster, with the median latency going from 306.01 µs to 241.90 µs (~21% faster on this branch).

Note that the older try_add_sample path is slower, which is something to evaluate. I hypothesized that it wouldn't change much: it was interning before, and it's interning still, just in a ProfilesDictionary instead. However, the thread-safety of the ProfilesDictionary slows it down more than I expected.

Additional Notes

I intend to have AI help me migrate the Ruby profiler to test these changes.

How to test the change?

For now, don't. This is a work-in-progress.

morrisonlevi and others added 9 commits February 17, 2026 16:34
Ensure Profile::try_new_internal always creates and stores a
ProfilesDictionaryTranslator when one is not provided.

This guarantees dictionary-backed api2 paths are available from
profile construction time and removes constructor-time optional
behavior.

Add a focused unit test that verifies Profile::new initializes the
profiles dictionary.
Convert legacy api::Sample inputs into api2 structures via the
profile dictionary, then route try_add_sample through
try_add_sample2. This unifies ingestion behavior across APIs while
keeping dictionary-backed ids on a single translation path.

Remove the now-unused legacy try_add_mapping/function/location
helpers from the old ingestion path.
Extend StringTable with an optional ProfilesDictionary attachment
and add try_intern_string_id2/try_intern_string_ref for
dictionary-backed ids. Return a dedicated
ProfilesDictionaryRequired error when no dictionary is attached.

Attach each Profile dictionary to its string table at construction
so dictionary-backed interning can reuse dictionary bytes safely.
Add focused tests for required-dictionary errors and dictionary
interning behavior.
…ed internals

Consolidate profiling and profiling-ffi around dictionary-backed ids, remove
legacy managed-string and generational interning code paths, and update tests,
fuzzing, and benchmarks for the new profile add-sample flow.

Co-authored-by: Cursor <cursoragent@cursor.com>
Make ProfilesDictionary mandatory in StringTable construction and remove
optional attach/error paths so dictionary-backed interning follows one invariant.

Co-authored-by: Cursor <cursoragent@cursor.com>
Revert small doc and derive differences in FunctionId2, MappingId2, and
StringId2 so this branch only carries the intended profiling refactor work.

Co-authored-by: Cursor <cursoragent@cursor.com>
Restore explicit common.h include in profile_intern.cpp, simplify a redundant
api import style, and improve StringTable test expect messages. Also drop the
cxx profiling comment tweak to keep this PR focused. Fix clippy warnings.

Co-authored-by: Cursor <cursoragent@cursor.com>
Implement Send at the Profile container level rather than on raw-pointer id
handles, and document why this is safe with dictionary-owned stable storage.
Add a compile-time test asserting Profile implements Send.

Co-authored-by: Cursor <cursoragent@cursor.com>
Update the interning_strings benchmark to construct a ProfilesDictionary Arc
and pass it into StringTable::new so benches compile with the required
dictionary-backed constructor.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions
Copy link

github-actions bot commented Feb 18, 2026

📚 Documentation Check Results

⚠️ 560 documentation warning(s) found

📦 libdd-profiling - 560 warning(s)


Updated: 2026-02-18 00:12:58 UTC | Commit: df63d6b | missing-docs job results

@github-actions
Copy link

Clippy Allow Annotation Report

Comparing clippy allow annotations between branches:

Summary by Rule

Rule Base Branch PR Branch Change
expect_used 5 2 ✅ -3 (-60.0%)
unwrap_used 1 1 No change (0%)
Total 6 3 ✅ -3 (-50.0%)

Annotation Counts by File

File Base Branch PR Branch Change
libdd-profiling/src/collections/string_storage.rs 1 0 ✅ -1 (-100.0%)
libdd-profiling/src/collections/string_table/mod.rs 1 1 No change (0%)
libdd-profiling/src/internal/function.rs 1 0 ✅ -1 (-100.0%)
libdd-profiling/src/internal/location.rs 1 1 No change (0%)
libdd-profiling/src/internal/mapping.rs 1 0 ✅ -1 (-100.0%)
libdd-profiling/src/internal/profile/mod.rs 1 1 No change (0%)

Annotation Stats by Crate

Crate Base Branch PR Branch Change
clippy-annotation-reporter 5 5 No change (0%)
datadog-ffe-ffi 1 1 No change (0%)
datadog-ipc 27 27 No change (0%)
datadog-live-debugger 6 6 No change (0%)
datadog-live-debugger-ffi 10 10 No change (0%)
datadog-profiling-replayer 4 4 No change (0%)
datadog-remote-config 3 3 No change (0%)
datadog-sidecar 59 59 No change (0%)
libdd-common 10 10 No change (0%)
libdd-common-ffi 12 12 No change (0%)
libdd-crashtracker 12 12 No change (0%)
libdd-data-pipeline 6 6 No change (0%)
libdd-ddsketch 2 2 No change (0%)
libdd-dogstatsd-client 1 1 No change (0%)
libdd-profiling 10 10 No change (0%)
libdd-telemetry 19 19 No change (0%)
libdd-tinybytes 4 4 No change (0%)
libdd-trace-normalization 2 2 No change (0%)
libdd-trace-obfuscation 9 9 No change (0%)
libdd-trace-utils 15 15 No change (0%)
Total 217 217 No change (0%)

About This Report

This report tracks Clippy allow annotations for specific rules, showing how they've changed in this PR. Decreasing the number of these annotations generally improves code quality.

@morrisonlevi morrisonlevi added profiling Relates to the profiling* modules. breaking-change labels Feb 18, 2026
@github-actions
Copy link

github-actions bot commented Feb 18, 2026

🔒 Cargo Deny Results

⚠️ 1 issue(s) found, showing only errors (advisories, bans, sources)

📦 libdd-profiling - 1 error(s)

Show output
error[vulnerability]: Integer overflow in `BytesMut::reserve`
   ┌─ /home/runner/work/libdatadog/libdatadog/Cargo.lock:30:1
   │
30 │ bytes 1.8.0 registry+https://github.com/rust-lang/crates.io-index
   │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ security vulnerability detected
   │
   ├ ID: RUSTSEC-2026-0007
   ├ Advisory: https://rustsec.org/advisories/RUSTSEC-2026-0007
   ├ In the unique reclaim path of `BytesMut::reserve`, the condition
     ```rs
     if v_capacity >= new_cap + offset
     ```
     uses an unchecked addition. When `new_cap + offset` overflows `usize` in release builds, this condition may incorrectly pass, causing `self.cap` to be set to a value that exceeds the actual allocated capacity. Subsequent APIs such as `spare_capacity_mut()` then trust this corrupted `cap` value and may create out-of-bounds slices, leading to UB.
     
     This behavior is observable in release builds (integer overflow wraps), whereas debug builds panic due to overflow checks.
     
     ## PoC
     
     ```rs
     use bytes::*;
     
     fn main() {
         let mut a = BytesMut::from(&b"hello world"[..]);
         let mut b = a.split_off(5);
     
         // Ensure b becomes the unique owner of the backing storage
         drop(a);
     
         // Trigger overflow in new_cap + offset inside reserve
         b.reserve(usize::MAX - 6);
     
         // This call relies on the corrupted cap and may cause UB & HBO
         b.put_u8(b'h');
     }
     ```
     
     # Workarounds
     
     Users of `BytesMut::reserve` are only affected if integer overflow checks are configured to wrap. When integer overflow is configured to panic, this issue does not apply.
   ├ Announcement: https://github.com/advisories/GHSA-434x-w66g-qw3r
   ├ Solution: Upgrade to >=1.11.1 (try `cargo update -p bytes`)
   ├ bytes v1.8.0
     ├── combine v4.6.7
     │   └── jni v0.21.1
     │       └── rustls-platform-verifier v0.6.2
     │           └── reqwest v0.13.1
     │               ├── libdd-common v1.1.0
     │               │   └── libdd-profiling v1.0.0
     │               │       └── (dev) libdd-profiling v1.0.0 (*)
     │               └── libdd-profiling v1.0.0 (*)
     ├── http v1.1.0
     │   ├── http-body v1.0.1
     │   │   ├── http-body-util v0.1.2
     │   │   │   ├── libdd-common v1.1.0 (*)
     │   │   │   ├── libdd-profiling v1.0.0 (*)
     │   │   │   └── reqwest v0.13.1 (*)
     │   │   ├── hyper v1.6.0
     │   │   │   ├── hyper-rustls v0.27.3
     │   │   │   │   ├── libdd-common v1.1.0 (*)
     │   │   │   │   └── reqwest v0.13.1 (*)
     │   │   │   ├── hyper-util v0.1.17
     │   │   │   │   ├── hyper-rustls v0.27.3 (*)
     │   │   │   │   ├── libdd-common v1.1.0 (*)
     │   │   │   │   └── reqwest v0.13.1 (*)
     │   │   │   ├── libdd-common v1.1.0 (*)
     │   │   │   └── reqwest v0.13.1 (*)
     │   │   ├── hyper-util v0.1.17 (*)
     │   │   ├── libdd-common v1.1.0 (*)
     │   │   ├── reqwest v0.13.1 (*)
     │   │   └── tower-http v0.6.8
     │   │       └── reqwest v0.13.1 (*)
     │   ├── http-body-util v0.1.2 (*)
     │   ├── hyper v1.6.0 (*)
     │   ├── hyper-rustls v0.27.3 (*)
     │   ├── hyper-util v0.1.17 (*)
     │   ├── libdd-common v1.1.0 (*)
     │   ├── libdd-profiling v1.0.0 (*)
     │   ├── multer v3.1.0
     │   │   └── libdd-common v1.1.0 (*)
     │   ├── reqwest v0.13.1 (*)
     │   └── tower-http v0.6.8 (*)
     ├── http-body v1.0.1 (*)
     ├── http-body-util v0.1.2 (*)
     ├── hyper v1.6.0 (*)
     ├── hyper-util v0.1.17 (*)
     ├── libdd-common v1.1.0 (*)
     ├── libdd-profiling v1.0.0 (*)
     ├── multer v3.1.0 (*)
     ├── prost v0.14.3
     │   ├── libdd-profiling v1.0.0 (*)
     │   └── libdd-profiling-protobuf v1.0.0
     │       ├── libdd-profiling v1.0.0 (*)
     │       └── (dev) libdd-profiling-protobuf v1.0.0 (*)
     ├── reqwest v0.13.1 (*)
     ├── tokio v1.49.0
     │   ├── hyper v1.6.0 (*)
     │   ├── hyper-rustls v0.27.3 (*)
     │   ├── hyper-util v0.1.17 (*)
     │   ├── (dev) libdd-common v1.1.0 (*)
     │   ├── libdd-profiling v1.0.0 (*)
     │   ├── reqwest v0.13.1 (*)
     │   ├── tokio-rustls v0.26.0
     │   │   ├── hyper-rustls v0.27.3 (*)
     │   │   ├── libdd-common v1.1.0 (*)
     │   │   └── reqwest v0.13.1 (*)
     │   ├── tokio-util v0.7.12
     │   │   └── libdd-profiling v1.0.0 (*)
     │   └── tower v0.5.2
     │       ├── reqwest v0.13.1 (*)
     │       └── tower-http v0.6.8 (*)
     ├── tokio-util v0.7.12 (*)
     └── tower-http v0.6.8 (*)

advisories FAILED, bans ok, sources ok

Updated: 2026-02-18 00:16:24 UTC | Commit: df63d6b | dependency-check job results

@codecov-commenter
Copy link

Codecov Report

❌ Patch coverage is 93.12039% with 28 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.36%. Comparing base (65b13e4) to head (2d90f97).

Additional details and impacted files
@@                  Coverage Diff                  @@
##           levi/api2-eq-hash    #1594      +/-   ##
=====================================================
+ Coverage              70.90%   71.36%   +0.45%     
=====================================================
  Files                    424      418       -6     
  Lines                  62053    61375     -678     
=====================================================
- Hits                   44001    43802     -199     
+ Misses                 18052    17573     -479     
Components Coverage Δ
libdd-crashtracker 62.41% <ø> (-0.02%) ⬇️
libdd-crashtracker-ffi 15.80% <ø> (ø)
libdd-alloc 98.77% <ø> (ø)
libdd-data-pipeline 85.96% <ø> (ø)
libdd-data-pipeline-ffi 75.63% <ø> (ø)
libdd-common 79.79% <ø> (ø)
libdd-common-ffi 73.75% <ø> (ø)
libdd-telemetry 62.52% <ø> (ø)
libdd-telemetry-ffi 16.75% <ø> (ø)
libdd-dogstatsd-client 82.64% <ø> (ø)
datadog-ipc 80.71% <ø> (ø)
libdd-profiling 85.60% <93.12%> (+4.19%) ⬆️
libdd-profiling-ffi 73.37% <100.00%> (+9.70%) ⬆️
datadog-sidecar 32.76% <ø> (ø)
datdog-sidecar-ffi 9.50% <ø> (ø)
spawn-worker 54.69% <ø> (ø)
libdd-tinybytes 93.16% <ø> (ø)
libdd-trace-normalization 81.71% <ø> (ø)
libdd-trace-obfuscation 94.18% <ø> (ø)
libdd-trace-protobuf 68.00% <ø> (ø)
libdd-trace-utils 88.72% <ø> (ø)
datadog-tracer-flare 88.95% <ø> (ø)
libdd-log 74.69% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking-change profiling Relates to the profiling* modules.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants