Skip to content

feat(nativemem): categorized native-memory accounting — first cut#669

Draft
rkennke wants to merge 2 commits into
mainfrom
feat/native-mem-accounting
Draft

feat(nativemem): categorized native-memory accounting — first cut#669
rkennke wants to merge 2 commits into
mainfrom
feat/native-mem-accounting

Conversation

@rkennke

@rkennke rkennke commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

What

Adds a NativeMem facility to measure the profiler's own native memory usage — currently we only observe whole-process RSS. This is the first cut of a phased effort: get a per-category live gauge plus a moving-window average and a running max, wired through the existing counter/JFR path.

Why

We measure the agent's native footprint only as an RSS delta (~150–200 MB) with no breakdown. This gives us an in-process, attributable number we can categorize and trend, and a foundation for later identifying which code is responsible.

How

  • NativeMem (nativeMem.h / nativeMem.cpp): a NativeMemCategory enum whose per-category live gauges partition the total — each backing allocation belongs to exactly one category, so there's no double-counting.
  • Always-on, independent of the COUNTERS build flag: record() is a single relaxed atomic add, which is async-signal-safe (usable from signal handlers).
  • sample() folds the live gauges into a moving-window average and a high-water max, ticked once per JFR chunk finish (Recording::finishChunk).
  • Exposure for free: totals mirror into the existing NATIVE_MEM_{LIVE,AVG,MAX}_BYTES counters (JFR T_DATADOG_COUNTER + JNI debug counters); per-category values are emitted as native_mem_{live,avg,max}_bytes.<category> events reusing the same event format — no new event type or schema change.
  • Instrumented sites (tagged CALLTRACE, their sole use today): LinearAllocator chunk alloc/free (the call-trace arena) and the per-shard calltrace buffers. Accounting lives with the semantic owner rather than in OS::safeAlloc, which stays category-agnostic.

Avoiding double-counting

The call-trace bytes are visible at three layers today — safeAlloc mmap, LINEAR_ALLOCATOR_BYTES (reserved chunks), and CALLTRACE_STORAGE_BYTES (used within chunks). Summing counters would double/triple-count. The new aggregate counts backing memory once per category; the existing reserved/used/waste counters (CALLTRACE_STORAGE_BYTES, DICTIONARY_ARENA_WASTE_BYTES) remain an independent nested dimension and are intentionally not summed into the per-category total.

First-cut limitations (documented in code)

  • Only CALLTRACE sites are instrumented; other categories exist but read 0 until their malloc/calloc/new sites are tagged (or a future malloc-interception pass captures them). Expect the total to be a correct subset of the RSS delta.
  • Sampled max, not spike-accurate — peaks entirely between two chunk finishes aren't seen. Alloc-time high-water tracking is the tightening step.
  • Transient negative live (from any not-yet-paired free) is clamped to 0 so it can't skew stats.

Roadmap

  1. This PR — facility + CALLTRACE coverage + total/per-category avg/max.
  2. Tag the remaining categories' backing sites; add spike-accurate max.
  3. Malloc interception to capture the untagged long tail so categories sum up to the RSS delta.

Testing

  • New nativeMem_ut (6 tests): live tracking + total partition, single-sample avg==live, moving average over ticks, max high-water behavior, negative-live clamping, category-name presence.
  • Full :ddprof-lib:gtestDebug suite green — 335 tests, 0 failures.

🤖 Generated with Claude Code

Add a NativeMem facility that tracks the profiler's own native memory
usage per category, with a moving-window average and a running peak.

- NativeMemCategory enum whose per-category live gauges partition the
  total (each backing allocation belongs to exactly one category, so
  there is no double counting).
- Live accounting is always-on and independent of the COUNTERS build
  flag: record() is a single relaxed atomic add, async-signal-safe and
  usable from signal handlers.
- sample() folds the live gauges into a moving-window average and a
  high-water max; it is ticked once per JFR chunk finish.
- Totals mirror into the existing NATIVE_MEM_{LIVE,AVG,MAX}_BYTES
  counters (JFR + JNI counter path); per-category values are emitted as
  native_mem_{live,avg,max}_bytes.<category> counter events, reusing the
  existing counter event format (no new event type).

Instrumented sites (tagged CALLTRACE, their sole use today): the
LinearAllocator chunk alloc/free (the call-trace arena) and the
per-shard calltrace buffers. Accounting lives with the semantic owner
rather than OS::safeAlloc, which stays category-agnostic.

First-cut limits, documented in code: only CALLTRACE sites are
instrumented so far (other categories read 0 until tagged); the max is
sampled rather than spike-accurate; transient negative live is clamped
to 0. The existing reserved/used/waste counters (CALLTRACE_STORAGE_BYTES,
DICTIONARY_ARENA_WASTE_BYTES) remain an independent nested dimension and
are intentionally not summed into the per-category total.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dd-octo-sts

dd-octo-sts Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

CI Test Results

Run: #29589359986 | Commit: 3352c1e | Duration: 16m 19s (longest job)

All 32 test jobs passed

Status Overview

JDK glibc-aarch64/debug glibc-amd64/debug musl-aarch64/debug musl-amd64/debug
8 - - -
8-ibm - - -
8-j9 - -
8-librca - -
8-orcl - - -
11 - - -
11-j9 - -
11-librca - -
17 - -
17-graal - -
17-j9 - -
17-librca - -
21 - -
21-graal - -
21-librca - -
25 - -
25-graal - -
25-librca - -

Legend: ✅ passed | ❌ failed | ⚪ skipped | 🚫 cancelled

Summary: Total: 32 | Passed: 32 | Failed: 0


Updated: 2026-07-17 15:06:13 UTC

@dd-octo-sts

dd-octo-sts Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Benchmark Results (commit e095203)

Pipeline: https://gitlab.ddbuild.io/DataDog/apm-reliability/benchmarking-platform/-/pipelines/125289326 Commit: e095203a546caadc5ac26755383d0b2880957cf7

⚠️ Significant outliers

  • 🟢 future-genetic (JDK 21): runtime -3.6% (2121→2045 ms)
  • 🔴 future-genetic (JDK 25): runtime +5.6% (1976→2086 ms)
Runtime details (per benchmark × JDK)
Benchmark JDK Latest Dev Δ (dev vs latest) Issues L/D
akka-uct 21 ✅ 10287 ms (21 iters) ✅ 10260 ms (21 iters) ≈ -0.3% (±10.9%) — / —
akka-uct 25 ✅ 8867 ms (24 iters) ✅ 8805 ms (24 iters) ≈ -0.7% (±9.8%) — / —
finagle-chirper 21 ✅ 5933 ms (33 iters) ✅ 5935 ms (33 iters) ≈ +0% (±24.9%) ⚠️ W:3 / ⚠️ W:3
finagle-chirper 25 ✅ 5475 ms (36 iters) ✅ 5453 ms (36 iters) ≈ -0.4% (±24.5%) ⚠️ W:3 / ⚠️ W:3
fj-kmeans 21 ✅ 2648 ms (71 iters) ✅ 2700 ms (69 iters) ≈ +2% (±2.7%) — / —
fj-kmeans 25 ✅ 2825 ms (66 iters) ✅ 2836 ms (66 iters) ≈ +0.4% (±2.6%) — / —
future-genetic 21 ✅ 2121 ms (88 iters) ✅ 2045 ms (90 iters) 🟢 -3.6% — / —
future-genetic 25 ✅ 1976 ms (94 iters) ✅ 2086 ms (89 iters) 🔴 +5.6% — / —
naive-bayes 21 ✅ 1230 ms (139 iters) ✅ 1259 ms (137 iters) ≈ +2.4% (±32.9%) — / —
naive-bayes 25 ✅ 1015 ms (168 iters) ✅ 1011 ms (169 iters) ≈ -0.4% (±31.6%) — / —
reactors 21 ✅ 15811 ms (16 iters) ✅ 16445 ms (15 iters) ≈ +4% (±7.6%) — / —
reactors 25 ✅ 18243 ms (15 iters) ✅ 18764 ms (15 iters) ≈ +2.9% (±4.1%) — / —
Internal counter details (ddprof)

ddprof internal counters, latest / dev (✅ = 0, · = unavailable):

Benchmark JDK Dropped rec Dropped jvmti Dropped trace Skipped WC AGCT fail Unwind fail
akka-uct 21 ✅ / ✅ ✅ / ✅ 3 / 1 2094 / 1933 ✅ / ✅ ✅ / ✅
akka-uct 25 ✅ / ✅ ✅ / ✅ 1 / 3 2216 / 2195 ✅ / ✅ ✅ / ✅
finagle-chirper 21 ✅ / ✅ ✅ / ✅ 4 / 2 8790 / 8777 ✅ / ✅ ✅ / ✅
finagle-chirper 25 ✅ / ✅ ✅ / ✅ 1 / 3 8165 / 8254 ✅ / ✅ ✅ / ✅
fj-kmeans 21 ✅ / ✅ ✅ / ✅ 2 / 2 1283 / 1240 ✅ / ✅ ✅ / ✅
fj-kmeans 25 ✅ / ✅ ✅ / ✅ 1 / 2 1289 / 1281 ✅ / ✅ ✅ / ✅
future-genetic 21 ✅ / ✅ ✅ / ✅ ✅ / 2 2935 / 2926 ✅ / ✅ ✅ / ✅
future-genetic 25 ✅ / ✅ ✅ / ✅ 1 / 2 2884 / 2927 ✅ / ✅ ✅ / ✅
naive-bayes 21 ✅ / ✅ ✅ / ✅ 4 / 2 3486 / 3580 ✅ / ✅ ✅ / ✅
naive-bayes 25 ✅ / ✅ ✅ / ✅ 5 / ✅ 3448 / 3533 ✅ / ✅ ✅ / ✅
reactors 21 ✅ / ✅ ✅ / ✅ 1 / ✅ 1512 / 1844 ✅ / ✅ ✅ / ✅
reactors 25 ✅ / ✅ ✅ / ✅ 1 / 1 1866 / 1889 ✅ / ✅ ✅ / ✅

@datadog-prod-us1-5

This comment has been minimized.

@dd-octo-sts

dd-octo-sts Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Reliability & Chaos Results

2 failure(s) detected Pipeline: https://gitlab.ddbuild.io/DataDog/java-profiler/-/pipelines/125289287

❌ chaos: profiler tcmalloc amd64 21 0 3 temXchaos
Chaos harness crashed (RC=124)
siginfo: si_signo: 11 (SIGSEGV), si_code: 1 (SEGV_MAPERR), si_addr: 0x0000000000000201
# C  [libjavaProfiler-dd-tmp1651948136414155440.so+0x14200]
❌ chaos: profiler tracer tcmalloc aarch64 25 0 3 temXchaos
Chaos harness crashed (RC=124)

@dd-octo-sts

dd-octo-sts Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Benchmark Results (commit 62b6f2c)

Pipeline: https://gitlab.ddbuild.io/DataDog/apm-reliability/benchmarking-platform/-/pipelines/125313150 Commit: 62b6f2cdc90e727ad72f0bcc5a37ef184201f0f7

⚠️ Significant outliers

  • 🟢 future-genetic (JDK 21): runtime -2.6% (2122→2066 ms)
  • 🟢 future-genetic (JDK 25): runtime -5.4% (2071→1960 ms)
Runtime details (per benchmark × JDK)
Benchmark JDK Latest Dev Δ (dev vs latest) Issues L/D
akka-uct 21 ✅ 10303 ms (21 iters) ✅ 10356 ms (21 iters) ≈ +0.5% (±10.7%) — / —
akka-uct 25 ✅ 9028 ms (24 iters) ✅ 8895 ms (24 iters) ≈ -1.5% (±9.8%) — / —
finagle-chirper 21 ✅ 5921 ms (33 iters) ✅ 5936 ms (33 iters) ≈ +0.3% (±25.2%) ⚠️ W:3 / ⚠️ W:3
finagle-chirper 25 ✅ 5537 ms (36 iters) ✅ 5535 ms (36 iters) ≈ -0% (±24.4%) ⚠️ W:3 / ⚠️ W:3
fj-kmeans 21 ✅ 2705 ms (70 iters) ✅ 2667 ms (69 iters) ≈ -1.4% (±2.6%) — / —
fj-kmeans 25 ✅ 2829 ms (66 iters) ✅ 2824 ms (66 iters) ≈ -0.2% (±2.6%) — / —
future-genetic 21 ✅ 2122 ms (88 iters) ✅ 2066 ms (90 iters) 🟢 -2.6% — / —
future-genetic 25 ✅ 2071 ms (90 iters) ✅ 1960 ms (94 iters) 🟢 -5.4% — / —
naive-bayes 25 ✅ 1022 ms (167 iters) ✅ 1012 ms (169 iters) ≈ -1% (±31.6%) — / —
reactors 21 ✅ 16191 ms (15 iters) ✅ 15952 ms (15 iters) ≈ -1.5% (±6.7%) — / —
reactors 25 ✅ 18581 ms (15 iters) ✅ 18426 ms (15 iters) ≈ -0.8% (±3.8%) — / —
Internal counter details (ddprof)

ddprof internal counters, latest / dev (✅ = 0, · = unavailable):

Benchmark JDK Dropped rec Dropped jvmti Dropped trace Skipped WC AGCT fail Unwind fail
akka-uct 21 ✅ / ✅ ✅ / ✅ 2 / ✅ 1975 / 2029 ✅ / ✅ ✅ / ✅
akka-uct 25 ✅ / ✅ ✅ / ✅ ✅ / ✅ 2406 / 2283 ✅ / ✅ ✅ / ✅
finagle-chirper 21 ✅ / ✅ ✅ / ✅ 7 / 7 8749 / 8443 ✅ / ✅ ✅ / ✅
finagle-chirper 25 ✅ / ✅ ✅ / ✅ 1 / 2 8539 / 8755 ✅ / ✅ ✅ / ✅
fj-kmeans 21 ✅ / ✅ ✅ / ✅ 5 / 3 1305 / 1244 ✅ / ✅ ✅ / ✅
fj-kmeans 25 ✅ / ✅ ✅ / ✅ 4 / 1 1284 / 1292 ✅ / ✅ ✅ / ✅
future-genetic 21 ✅ / ✅ ✅ / ✅ ✅ / 1 3065 / 2961 ✅ / ✅ ✅ / ✅
future-genetic 25 ✅ / ✅ ✅ / ✅ 2 / 4 2938 / 2818 ✅ / ✅ ✅ / ✅
naive-bayes 25 ✅ / ✅ ✅ / ✅ 5 / 4 3483 / 3485 ✅ / ✅ ✅ / ✅
reactors 21 ✅ / ✅ ✅ / ✅ 1 / ✅ 1495 / 1554 ✅ / ✅ ✅ / ✅
reactors 25 ✅ / ✅ ✅ / ✅ 1 / ✅ 1874 / 1984 ✅ / ✅ ✅ / ✅

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants