Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ libdd-crashtracker*/ @DataDog/libdatadog-profiling
libdd-data-pipeline*/ @DataDog/libdatadog-apm
libdd-ddsketch*/ @DataDog/libdatadog-apm @DataDog/apm-common-components-core
libdd-dogstatsd-client @DataDog/apm-common-components-core
libdd-heap-*/ @DataDog/libdatadog-profiling
libdd-http-client @DataDog/apm-common-components-core
libdd-agent-client @DataDog/apm-common-components-core
libdd-library-config*/ @DataDog/apm-sdk-capabilities-rust
Expand Down
49 changes: 49 additions & 0 deletions .github/workflows/verify-heap-sampler-bindings.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: 'Verify libdd-heap-sampler bindings'
on:
pull_request:
types: [opened, synchronize, reopened]
paths:
- 'libdd-heap-sampler/**'
- '.github/workflows/verify-heap-sampler-bindings.yml'
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
jobs:
verify-bindings:
name: "Verify libdd-heap-sampler generated bindings are in sync"
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2
- name: Install libclang-dev
# Only this verification job needs libclang; the normal build
# path for libdd-heap-sampler consumes the checked-in bindings
# under `src/generated/` and does not depend on bindgen at all.
run: |
sudo apt-get update
sudo apt-get install -y libclang-dev
- name: Read Rust version from rust-toolchain.toml
id: rust-version
run: echo "version=$(grep -Po '^channel = "\K[^"]+' rust-toolchain.toml)" >> $GITHUB_OUTPUT
- name: Install ${{ steps.rust-version.outputs.version }} toolchain
run: rustup set profile minimal && rustup install ${{ steps.rust-version.outputs.version }} && rustup default ${{ steps.rust-version.outputs.version }}
- name: Cache [rust]
uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # 2.8.1
with:
cache-targets: true
cache-bin: true
- name: Regenerate bindings
# `LIBDD_HEAP_SAMPLER_REGEN=1` rewrites `src/generated/*`
# in-place via bindgen. If the committed files were stale the
# git diff below fails and instructs the author how to refresh
# them. We use an env var (rather than a cargo feature) so that
# unrelated `--all-features` CI jobs cannot accidentally invoke
# bindgen — see libdd-heap-sampler/Cargo.toml for the rationale.
run: LIBDD_HEAP_SAMPLER_REGEN=1 cargo build -p libdd-heap-sampler
- name: Verify committed bindings match regenerated output
run: |
if ! git diff --exit-code libdd-heap-sampler/src/generated/; then
echo "::error::libdd-heap-sampler/src/generated/ is out of date."
echo "Run \`LIBDD_HEAP_SAMPLER_REGEN=1 cargo build -p libdd-heap-sampler\` locally and commit the result."
exit 1
fi
59 changes: 58 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
members = [
"builder",
"libdd-alloc",
"libdd-heap-sampler",
"libdd-heap-allocator",
"libdd-heap-gotter",
"libdd-heap-gotter-ffi",
"libdd-crashtracker",
"libdd-crashtracker-ffi",
"datadog-ffe",
Expand Down
8 changes: 8 additions & 0 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,11 @@ Datadog libdatadog
Copyright 2021-2022 Datadog, Inc.

This product includes software developed at Datadog (<https://www.datadoghq.com/>).

--

This product bundles a copy of the libbpf/usdt single-header USDT
library in `libdd-heap-sampler/vendor/usdt.h`. That file is licensed
under the BSD 2-Clause License, Copyright (c) 2024 Meta Platforms, Inc.
and affiliates. The SPDX identifier and copyright notice are retained
verbatim in the file header. Upstream: <https://github.com/libbpf/usdt>.
26 changes: 26 additions & 0 deletions libdd-heap-allocator/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
# SPDX-License-Identifier: Apache-2.0

[package]
name = "libdd-heap-allocator"
version = "0.1.0"
description = "Rust GlobalAlloc wrapper that drives libdd-heap-sampler."
homepage = "https://github.com/DataDog/libdatadog/tree/main/libdd-heap-allocator"
repository = "https://github.com/DataDog/libdatadog/tree/main/libdd-heap-allocator"
edition.workspace = true
rust-version.workspace = true
license.workspace = true
publish = false

[lib]
bench = false

[dependencies]
libdd-heap-sampler = { path = "../libdd-heap-sampler" }

[dev-dependencies]
criterion = "0.5.1"

[[bench]]
name = "sampler_overhead"
harness = false
49 changes: 49 additions & 0 deletions libdd-heap-allocator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# libdd-heap-allocator

Rust `GlobalAlloc` wrapper with USDT-based heap profiling, effectively implementing [libdd-heap-sampler](../libdd-heap-sampler) for Rust apps at compile time. This lets Rust users quickly setup sampled heap profiling within their application regardless of the particular allocator they are using.

For this to work _well_, you should make sure everything passes through the global allocator!

Usage:

```rust
use libdd_heap_allocator::SampledAllocator;
use std::alloc::System;

// Wrap the default system allocator
#[global_allocator]
static ALLOC: SampledAllocator<System> = SampledAllocator::<System>::DEFAULT;
```

To wrap a custom allocator instead:

```rust
#[global_allocator]
static ALLOC: SampledAllocator<MyAllocator> = SampledAllocator::new(MyAllocator::new());
```

For profiling, prefer wrapping the allocator that is actually installed as the
process global allocator. Heap profiling is most useful when all allocations in
the process pass through the sampled wrapper.

See [`examples/usdt_demo.rs`](examples/usdt_demo.rs) for a runnable demo that fires USDT probes in a loop for `bpftrace` to observe.

## Benchmarking sampler overhead

The `sampler_overhead` Criterion benchmark measures the allocator/sampler hot path without installing `SampledAllocator` as the process global allocator. It compares direct `System` allocation, `SampledAllocator<System>`, a no-op allocator, `SampledAllocator<NoopAllocator>`, and direct sampler calls.

```sh
cargo bench -p libdd-heap-allocator --bench sampler_overhead
```

One quick validation run produced these fast-path results:

| Size | Base: `System` alloc/free | Sampled fast path | Overhead | Overhead % |
|---:|---:|---:|---:|---:|
| 16 B | 5.9719 ns | 10.916 ns | +4.9441 ns | +82.8% |
| 64 B | 5.9309 ns | 12.405 ns | +6.4741 ns | +109.2% |
| 256 B | 5.9639 ns | 10.827 ns | +4.8631 ns | +81.5% |
| 4096 B | 23.237 ns | 29.402 ns | +6.1650 ns | +26.5% |
| 65536 B | 23.880 ns | 28.496 ns | +4.6160 ns | +19.3% |

The no-op allocator comparison isolates the wrapper/sampler fast path at roughly **+5–6 ns per alloc/free pair**.
Loading
Loading