Repository navigation
Resolve mallinfo2 at runtime; fix always-zero malloc arena counters in el8 builds #918
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
26ec669
Resolve mallinfo2 at runtime so malloc arena counters survive old bui…
jbachorik 35fefc4
Run arena counter tests against the runtime-resolved mallinfo2 path; …
jbachorik 1dd97c2
Touch the mmapped test chunk so release builds cannot eliminate the a…
jbachorik c366afa
Document malloc arena counters availability and fallback semantics (P…
jbachorik 07fb43c
Apply spotless formatting to MallocArenaCountersTest (PROF-16350)
jbachorik efb2a4e
Anchor ldd version parse to the trailing token and bound the output r…
jbachorik f027b26
Quarantine LineNumberTableCopyTest.RejectsUnmappedSource on glibc-tsa…
jbachorik 64a395a
Pin the mirror include explicitly, cache the dlsym lookups, skip on i…
jbachorik 5133806
Publish the cached dlsym results with acquire/release instead of plai…
jbachorik File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| /* | ||
| * Copyright 2026, Datadog, Inc. | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| #include "mallocInfo.h" | ||
| #include <dlfcn.h> | ||
| #if defined(__linux__) | ||
| // The glibc guard below must see __GLIBC_PREREQ here, not via a transitive | ||
| // include: if <dlfcn.h> ever stopped pulling in <features.h>, the guard would | ||
| // silently turn false and the mirror pins would stop compiling on every | ||
| // glibc build. <features.h> exists on musl too, so the include is unconditional | ||
| // on Linux. | ||
| #include <features.h> | ||
| #endif | ||
|
|
||
| // mallinfo2() replaced the int-based mallinfo() in glibc 2.33. The release | ||
| // libraries are built against older glibc headers (the el8 image has 2.28), | ||
| // where <malloc.h> declares neither mallinfo2() nor its struct -- so instead | ||
| // of gating the call on __GLIBC_MINOR__ at compile time, which compiled the | ||
| // arena counters out of every shipped build, the symbol is resolved at | ||
| // runtime with dlsym(). A dlsym string lookup adds no symbol-version | ||
| // reference, so the binary keeps loading on runtimes older than 2.33: there | ||
| // dlsym returns NULL and the counters stay unset, exactly as on musl and | ||
| // macOS where the symbol does not exist either. | ||
| // | ||
| // The mirror must match the real struct field for field: mallinfo2() is | ||
| // called through this type, and the return-by-value ABI (hidden sret pointer | ||
| // vs registers) is selected from it. Ten size_t fields, same order as the | ||
| // old struct mallinfo; verified against glibc 2.33+ <malloc.h>. | ||
| struct Mallinfo2 { | ||
| size_t arena; | ||
| size_t ordblks; | ||
| size_t smblks; | ||
| size_t hblks; | ||
| size_t hblkhd; | ||
| size_t usmblks; | ||
| size_t fsmblks; | ||
| size_t uordblks; | ||
| size_t fordblks; | ||
| size_t keepcost; | ||
| }; | ||
|
|
||
| #if defined(__GLIBC__) && defined(__GLIBC_PREREQ) | ||
| #if __GLIBC_PREREQ(2, 33) | ||
| #include <malloc.h> | ||
| #include <stddef.h> | ||
| // On builds whose headers do declare struct mallinfo2, the mirror is | ||
| // pinned to it field by field: every one of the ten offsets, not just the | ||
| // five mallocArenaSnapshot() copies out (arena, hblkhd, uordblks, | ||
| // fordblks, keepcost). On older headers -- including the el8 release | ||
| // image (glibc 2.28), the builds this path exists for -- struct mallinfo2 | ||
| // is not declared, so this pin cannot run and the mirror is the only | ||
| // definition. There the field mapping is cross-checked at runtime by | ||
| // mallocArenaStats_ut.cpp wherever the test runtime provides mallinfo2: | ||
| // glibc's accounting identity (arena == uordblks + fordblks, keepcost <= | ||
| // fordblks), a large mmapped chunk moving only hblkhd, signed in-use | ||
| // deltas across an allocation burst, and post-trim keepcost bounds. On | ||
| // runtimes older than 2.33 those tests skip. | ||
| static_assert(sizeof(Mallinfo2) == sizeof(struct mallinfo2), | ||
| "Mallinfo2 mirror does not match glibc's struct mallinfo2"); | ||
| #define DDPROF_PIN_MALLINFO2_FIELD(f) \ | ||
| static_assert(offsetof(Mallinfo2, f) == offsetof(struct mallinfo2, f), \ | ||
| "Mallinfo2::" #f " offset diverges from glibc's struct mallinfo2") | ||
| DDPROF_PIN_MALLINFO2_FIELD(arena); | ||
| DDPROF_PIN_MALLINFO2_FIELD(ordblks); | ||
| DDPROF_PIN_MALLINFO2_FIELD(smblks); | ||
| DDPROF_PIN_MALLINFO2_FIELD(hblks); | ||
| DDPROF_PIN_MALLINFO2_FIELD(hblkhd); | ||
| DDPROF_PIN_MALLINFO2_FIELD(usmblks); | ||
| DDPROF_PIN_MALLINFO2_FIELD(fsmblks); | ||
| DDPROF_PIN_MALLINFO2_FIELD(uordblks); | ||
| DDPROF_PIN_MALLINFO2_FIELD(fordblks); | ||
| DDPROF_PIN_MALLINFO2_FIELD(keepcost); | ||
| #undef DDPROF_PIN_MALLINFO2_FIELD | ||
| #endif | ||
| #endif | ||
|
|
||
| bool mallocArenaSnapshot(MallocArenaInfo *out) { | ||
| typedef Mallinfo2 (*mallinfo2_fn)(); | ||
| // Resolved once. The RTLD_DEFAULT lookup result cannot change after libc is | ||
| // loaded, and the loader lock is not worth taking on every chunk flush while | ||
| // application threads may be inside dlopen(). The statics are zero- | ||
| // initialized (no dynamic-init guard), and the handoff is published with | ||
| // release/acquire: dlsym() is thread-safe, so racing first callers compute | ||
| // the same value; a concurrent reader can only repeat the lookup once. | ||
| // Flush path only, never signal context. | ||
| static mallinfo2_fn cached_fn = NULL; | ||
| static int resolved = 0; | ||
| if (__atomic_load_n(&resolved, __ATOMIC_ACQUIRE) == 0) { | ||
| __atomic_store_n(&cached_fn, (mallinfo2_fn)dlsym(RTLD_DEFAULT, "mallinfo2"), | ||
| __ATOMIC_RELEASE); | ||
| __atomic_store_n(&resolved, 1, __ATOMIC_RELEASE); | ||
| } | ||
| mallinfo2_fn fn = __atomic_load_n(&cached_fn, __ATOMIC_ACQUIRE); | ||
| if (fn == NULL) { | ||
| return false; | ||
| } | ||
| Mallinfo2 mi = fn(); | ||
| out->arena = mi.arena; | ||
| out->hblkhd = mi.hblkhd; | ||
| out->uordblks = mi.uordblks; | ||
| out->fordblks = mi.fordblks; | ||
| out->keepcost = mi.keepcost; | ||
| return true; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| /* | ||
| * Copyright 2026, Datadog, Inc. | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| #ifndef MALLOC_INFO_H | ||
| #define MALLOC_INFO_H | ||
|
|
||
| #include <stddef.h> | ||
|
|
||
| // The process-wide malloc arena figures the profiler publishes, in the units | ||
| // glibc's allocator accounting reports them (see mallocArenaSnapshot()). | ||
| struct MallocArenaInfo { | ||
| size_t arena; // system memory held by all arenas, summed per arena | ||
| size_t hblkhd; // bytes in mmap'd chunks | ||
| size_t uordblks; // bytes currently handed out to callers | ||
| size_t fordblks; // bytes free but retained inside the arenas | ||
| size_t keepcost; // the main arena's top chunk only | ||
| }; | ||
|
|
||
| // Snapshot the process-wide glibc malloc arena state via mallinfo2(), resolved | ||
| // at runtime with dlsym() so a binary built against old glibc headers (the el8 | ||
| // image ships glibc 2.28 headers) still picks mallinfo2() up from a newer | ||
| // runtime glibc. Fills *out and returns true on success; returns false and | ||
| // leaves *out untouched when the runtime allocator does not provide | ||
| // mallinfo2() (glibc < 2.33 at runtime, musl, macOS) -- callers leave the | ||
| // arena counters unset, exactly as they already do on musl. | ||
| // | ||
| // NOT async-signal-safe: mallinfo2() walks every arena taking each arena lock. | ||
| // Flush path only -- must never be reached from the sampling signal handler. | ||
| bool mallocArenaSnapshot(MallocArenaInfo *out); | ||
|
|
||
| #endif // MALLOC_INFO_H |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.