Skip to content

Commit e40c19b

Browse files
fix up local ffi; type-annotate
1 parent 6fab890 commit e40c19b

3 files changed

Lines changed: 35 additions & 29 deletions

File tree

setup.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,10 +1107,10 @@ def build_heap_gotter(self) -> None:
11071107
subprocess.run(["install_name_tool", "-id", gotter_name, gotter_library], check=True)
11081108

11091109
if self._should_strip_heap_gotter():
1110-
debug_sidecar = self._extract_and_strip_staged_debug_symbols(gotter_library)
1110+
debug_sidecar: t.Optional[Path] = self._extract_and_strip_staged_debug_symbols(gotter_library)
11111111
if debug_sidecar:
1112-
unstripped_size = built.stat().st_size
1113-
stripped_size = gotter_library.stat().st_size
1112+
unstripped_size: int = built.stat().st_size
1113+
stripped_size: int = gotter_library.stat().st_size
11141114
print(
11151115
f"Stripped heap-gotter cdylib for wheel packaging: "
11161116
f"{unstripped_size} -> {stripped_size} bytes "
@@ -1225,15 +1225,15 @@ def _extract_and_strip_staged_debug_symbols(so_file: Path) -> t.Optional[Path]:
12251225
are stripped post-build; heap-gotter is stripped at staging time because it
12261226
is built out-of-band from setuptools extensions).
12271227
"""
1228-
objcopy = shutil.which("objcopy")
1229-
strip_bin = shutil.which("strip")
1228+
objcopy: t.Optional[str] = shutil.which("objcopy")
1229+
strip_bin: t.Optional[str] = shutil.which("strip")
12301230
if not objcopy or not strip_bin:
12311231
print("WARNING: objcopy/strip not found, skipping heap-gotter symbol stripping", flush=True)
12321232
return None
12331233

1234-
so_path = str(so_file)
1234+
so_path: str = str(so_file)
12351235
subprocess.run([objcopy, "--remove-section", ".llvmbc", so_path], check=False)
1236-
debug_out = f"{so_path}.debug"
1236+
debug_out: str = f"{so_path}.debug"
12371237
try:
12381238
subprocess.run([objcopy, "--only-keep-debug", so_path, debug_out], check=True)
12391239
if not Path(debug_out).is_file() or Path(debug_out).stat().st_size == 0:

src/native_heap_gotter/Cargo.toml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ codegen-units = 1
3737
test-support = ["libdd-profiling-heap-gotter/test-support"]
3838

3939
[dependencies]
40-
# Pinned to the published crates.io release (Phase 1.5 migration off the
41-
# libdatadog `main` git pin). `default-features = false` keeps the
42-
# allocation-only surface: `live-heap` stays opt-in and off for Phase 1.
43-
libdd-profiling-heap-gotter = { version = "1.0.0", default-features = false }
40+
# Pinned to the published crates.io release. `live-heap` enables balanced
41+
# alloc/free sampling so the profiler can report retained (live) heap.
42+
libdd-profiling-heap-gotter = { version = "1.0.0", features = ["live-heap"] }

src/native_heap_gotter/lib.rs

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,8 @@
11
// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
22
// SPDX-License-Identifier: Apache-2.0
33

4-
//! Thin cdylib that wraps libdatadog's published `libdd-profiling-heap-gotter`
5-
//! crate under stable, ddtrace-owned C symbols. The Python ctypes activator
6-
//! (`ddtrace/internal/datadog/profiling/heap_gotter`) dlopen's the resulting
7-
//! `libdd_heap_gotter.<ext-suffix>.so` and drives GOT-based native heap
8-
//! profiling from it.
9-
//!
10-
//! The upstream crate exposes a pure-Rust API (`install_heap_overrides`,
11-
//! `heap_overrides_are_installed`); it is not a C-ABI surface. Here we re-export
12-
//! those calls as fixed, unmangled `extern "C"` entry points returning a plain
13-
//! `bool`, so the Python ctypes side links against stable symbol names and gets
14-
//! a trivial success signal.
15-
//!
16-
//! Installation is permanent and process-global: the GOT entries patched by
17-
//! `install` point at functions inside the linked-in gotter code, so this
18-
//! library must stay loaded for the life of the process. The Python activator
19-
//! loads it into the global namespace and never unloads it. After `fork()` the
20-
//! child inherits both the loaded library and the patched GOT, so a re-install
21-
//! in the child is a harmless no-op.
4+
//! Thin cdylib wrapping `libdd-profiling-heap-gotter` (crates.io) under stable
5+
//! `extern "C"` symbols for the Python ctypes activator to dlopen.
226
237
/// Install GOT overrides for supported heap-allocation symbols and report
248
/// whether the install actually took effect.
@@ -50,6 +34,29 @@ pub extern "C" fn ddtrace_heap_gotter_is_installed() -> bool {
5034
libdd_profiling_heap_gotter::heap_overrides_are_installed()
5135
}
5236

37+
/// Set the mean sample distance (bytes between samples) for the heap sampler.
38+
/// Must be called before `ddtrace_heap_gotter_install` to take effect.
39+
///
40+
/// # Safety
41+
///
42+
/// C ABI entry point; always safe to call.
43+
#[no_mangle]
44+
pub extern "C" fn ddtrace_heap_gotter_set_sampling_distance(distance: u64) {
45+
libdd_profiling_heap_gotter::set_default_sampling_distance(distance);
46+
}
47+
48+
/// Re-scan loaded libraries and patch any newly-introduced GOT entries.
49+
/// Normally called automatically from the internal `dlopen` hook; exposed here
50+
/// for cases where the Python side loads a `.so` and wants immediate coverage.
51+
///
52+
/// # Safety
53+
///
54+
/// C ABI entry point with no arguments and no pointers; always safe to call.
55+
#[no_mangle]
56+
pub extern "C" fn ddtrace_heap_gotter_update() {
57+
libdd_profiling_heap_gotter::update_heap_overrides();
58+
}
59+
5360
/// Test-only: number of times a patched hook has run in this process. Lets
5461
/// integration tests prove the patched GOT was actually exercised without a
5562
/// live eBPF attach. Only present when built with the `test-support` feature;

0 commit comments

Comments
 (0)