Skip to content

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

Draft
morrisonlevi wants to merge 10 commits intomainfrom
levi/profiling-remove-legacy-ids
Draft

perf(profiling)!: use Function2 and Mapping2 from ProfilesDictionary#1594
morrisonlevi wants to merge 10 commits intomainfrom
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 10 commits February 17, 2026 15:47
Add Eq/PartialEq/Hash for MappingId2, FunctionId2, and Location2 so api2
locations can be deduplicated by handle identity. Document that comparisons
are only intended for ids from the same ProfilesDictionary.

Co-authored-by: Cursor <cursoragent@cursor.com>
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.

Base automatically changed from levi/api2-eq-hash to main February 19, 2026 15:44
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