Skip to content

Commit bbd57cb

Browse files
feat(profiling): add native heap gotter Rust cdylib build (PR A)
Ship libdatadog heap-gotter as an opt-in cdylib built when DD_PROFILING_NATIVE_HEAP_BUILD=1. No runtime wiring yet.
1 parent 9bbc151 commit bbd57cb

5 files changed

Lines changed: 1927 additions & 1 deletion

File tree

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,10 @@ tests/appsec/iast/fixtures/taint_sinks/not_exists.txt
223223

224224
# Rust build artifacts
225225
src/native/target*
226+
src/native_heap_gotter/target*
227+
228+
# Profiling collector local CMake build dir
229+
ddtrace/profiling/collector/build-test/
226230

227231
# Fuzzing corpus, output and artifacts
228232
.fuzz/

setup.py

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@
113113
IS_EDITABLE = False # Set to True if the package is being installed in editable mode
114114

115115
NATIVE_CRATE = HERE / "src" / "native"
116+
# Standalone cdylib wrapper around libdatadog's heap-gotter FFI. Built out-of-band
117+
# (opt-in) because it pins libdatadog `main` rather than the tagged `src/native` rev.
118+
NATIVE_HEAP_GOTTER_CRATE = HERE / "src" / "native_heap_gotter"
116119
DDTRACE_DIR = HERE / "ddtrace"
117120
LIBDDWAF_DOWNLOAD_DIR = DDTRACE_DIR / "appsec" / "_ddwaf" / "libddwaf"
118121
IAST_DIR = DDTRACE_DIR / "appsec" / "_iast" / "_taint_tracking"
@@ -124,6 +127,14 @@
124127

125128
BUILD_PROFILING_NATIVE_TESTS = os.getenv("DD_PROFILING_NATIVE_TESTS", "0").lower() in ("1", "yes", "on", "true")
126129

130+
# Opt-in build of the native heap-gotter cdylib (Phase 1: allocation-only native
131+
# heap profiling via GOT rewriting, driven at runtime by the FH eBPF profiler).
132+
# Off by default so mainline wheels are not pinned to a moving libdatadog `main`
133+
# SHA and normal builds don't pay the extra cargo fetch/compile. The staging A/B
134+
# harness sets this to bake the artifact into its custom wheels; runtime install
135+
# is separately gated by DD_PROFILING_NATIVE_HEAP_ENABLED.
136+
BUILD_NATIVE_HEAP_GOTTER = os.getenv("DD_PROFILING_NATIVE_HEAP_BUILD", "0").lower() in ("1", "yes", "on", "true")
137+
127138
CURRENT_OS = platform.system()
128139
SERVERLESS_BUILD = os.getenv("DD_SERVERLESS_BUILD", "0").lower() in ("1", "yes", "on", "true")
129140
WHEEL_FLAVOR = "-serverless" if SERVERLESS_BUILD else ""
@@ -844,6 +855,13 @@ def run(self) -> None:
844855
with _time_phase("build_libdd_wrapper"):
845856
self.build_libdd_wrapper()
846857

858+
# Build the native heap-gotter cdylib (opt-in, Linux 64-bit only). It is
859+
# a standalone ctypes-loaded library with no dependency on the other
860+
# extensions, so ordering relative to them does not matter.
861+
if BUILD_NATIVE_HEAP_GOTTER and CURRENT_OS == "Linux" and is_64_bit_python():
862+
with _time_phase("build_heap_gotter"):
863+
self.build_heap_gotter()
864+
847865
# Build all declared shared C++ dependencies before extension builds.
848866
with _time_phase("build_shared_deps"):
849867
self.build_shared_deps()
@@ -1006,6 +1024,54 @@ def build_libdd_wrapper(self):
10061024
else:
10071025
print(f"Skipping libdd_wrapper build (no changes): {wrapper_name}")
10081026

1027+
def build_heap_gotter(self):
1028+
"""Build the native heap-gotter cdylib via cargo and stage it for packaging.
1029+
1030+
Produces ``libdd_heap_gotter<EXT_SUFFIX>.so`` under
1031+
``ddtrace/internal/datadog/profiling/`` (mirroring the ``_native`` /
1032+
``libdd_wrapper`` naming so the ctypes activator can resolve it with the
1033+
same EXT_SUFFIX logic). The wrapper crate has no Python linkage, so a
1034+
single ``target/`` dir is shared across interpreter versions.
1035+
"""
1036+
suffix = getattr(self, "suffix", None) or sysconfig.get_config_var("EXT_SUFFIX")
1037+
gotter_name = f"libdd_heap_gotter{suffix}"
1038+
1039+
if IS_EDITABLE or getattr(self, "inplace", False):
1040+
output_dir = Path(__file__).parent / "ddtrace" / "internal" / "datadog" / "profiling"
1041+
else:
1042+
output_dir = Path(__file__).parent / Path(self.build_lib) / "ddtrace" / "internal" / "datadog" / "profiling"
1043+
output_dir.mkdir(parents=True, exist_ok=True)
1044+
gotter_library = output_dir / gotter_name
1045+
1046+
cargo_cmd = [
1047+
"cargo",
1048+
"build",
1049+
"--release",
1050+
"--manifest-path",
1051+
str(NATIVE_HEAP_GOTTER_CRATE / "Cargo.toml"),
1052+
] + DD_CARGO_ARGS
1053+
subprocess.run(cargo_cmd, check=True)
1054+
1055+
# The wrapper crate's [lib] name is "dd_heap_gotter", so cargo emits a
1056+
# single-lib-prefixed artifact (unlike libdatadog's own liblib*.so).
1057+
# Glob defensively in case the extension/name assumption ever drifts.
1058+
release_dir = NATIVE_HEAP_GOTTER_CRATE / "target" / "release"
1059+
built = release_dir / "libdd_heap_gotter.so"
1060+
if not built.exists():
1061+
candidates = [c for c in release_dir.glob("libdd_heap_gotter.*") if c.suffix in (".so", ".dylib")]
1062+
if not candidates:
1063+
raise RuntimeError(f"Not able to find heap-gotter cdylib in {release_dir}")
1064+
built = candidates[0]
1065+
1066+
shutil.copy2(built, gotter_library)
1067+
print(f"Built and copied heap-gotter cdylib: {gotter_name}")
1068+
1069+
# Set SONAME so the loader records the staged name, matching build_rust.
1070+
if CURRENT_OS == "Linux":
1071+
subprocess.run(["patchelf", "--set-soname", gotter_name, gotter_library], check=True)
1072+
elif CURRENT_OS == "Darwin":
1073+
subprocess.run(["install_name_tool", "-id", gotter_name, gotter_library], check=True)
1074+
10091075
def build_shared_deps(self) -> None:
10101076
"""Build all shared C++ dependencies declared in SHARED_DEPS.
10111077
@@ -1750,7 +1816,9 @@ def get_exts_for(name):
17501816
"ddtrace.appsec.sca": ["_cve_data.json"],
17511817
"ddtrace.internal": ["third-party.tar.gz"],
17521818
"ddtrace.internal.datadog.profiling": (
1753-
["libdd_wrapper*.*"] + (["test/*"] if BUILD_PROFILING_NATIVE_TESTS else [])
1819+
["libdd_wrapper*.*"]
1820+
+ (["libdd_heap_gotter*.*"] if BUILD_NATIVE_HEAP_GOTTER else [])
1821+
+ (["test/*"] if BUILD_PROFILING_NATIVE_TESTS else [])
17541822
),
17551823
},
17561824
zip_safe=False,

0 commit comments

Comments
 (0)