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
27 changes: 27 additions & 0 deletions ddprof-lib/src/main/cpp/codeCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,33 @@ CodeCache::~CodeCache() {
free(_build_id); // Free build-id memory
}

long long CodeCache::memoryUsage() const {
// The blob array: _capacity entries of CodeBlob.
long long total = (long long)_capacity * sizeof(CodeBlob);

// This cache's own name, plus each symbol's name string. Each is a
// variable-length allocation whose size depends on the name length
// (see NativeFunc::allocSize). Both the name pointers and the array are fixed
// once the library is published (the same read the symbolication fast path
// does), so this is safe to read at dump time without extra synchronization.
total += (long long)NativeFunc::allocSize(_name);
for (int i = 0; i < _count; i++) {
total += (long long)NativeFunc::allocSize(_blobs[i]._name);
}

// The DWARF unwind table, when present (length only — no pointer deref).
total += (long long)_dwarf_table_length * sizeof(FrameDesc);

// The build-id string is intentionally NOT counted here: the background
// library refresher (Libraries::updateBuildIds) frees and replaces _build_id
// on already-published caches under _build_id_lock, which dump does not hold,
// so dereferencing it here would race. It is negligible (~tens of bytes per
// library) next to the symbol tables, so excluding it costs no meaningful
// accuracy while keeping this read lock-free.
Comment on lines +164 to +169

return total;
}

void CodeCache::expand() {
CodeBlob *old_blobs = _blobs;
CodeBlob *new_blobs = new CodeBlob[_capacity * 2];
Expand Down
39 changes: 32 additions & 7 deletions ddprof-lib/src/main/cpp/codeCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,15 @@ class NativeFunc {
static char *create(const char *name, short lib_index);
static void destroy(char *name);

// Size of the heap allocation backing a name string produced by create().
// Mirrors the size computed there so callers can account for it. 0 if null.
static size_t allocSize(const char *name) {
if (name == nullptr) {
return 0;
}
return align_up(sizeof(NativeFunc) + 1 + strlen(name), sizeof(NativeFunc *));
}

static short libIndex(const char *name) {
if (name == nullptr) {
return -1;
Expand Down Expand Up @@ -251,9 +260,14 @@ class CodeCache {
void setDwarfTable(FrameDesc *table, int length, const FrameDesc &default_frame = FrameDesc::default_frame);
FrameDesc findFrameDesc(const void *pc);

long long memoryUsage() {
return _capacity * sizeof(CodeBlob *) + _count * sizeof(NativeFunc);
}
// Live size of what this CodeCache owns on the heap: the blob array, the
// per-symbol name strings (variable length), this cache's own name, and the
// DWARF unwind table. Recomputed on demand (called only at dump time), so it
// reflects the current contents. The build-id string is deliberately excluded
// — it is mutated by the background refresher and negligible in size (see the
// definition). Const and lock-free: reads only fields that are stable once the
// library is published.
long long memoryUsage() const;

int count() { return _count; }
CodeBlob* blob(int idx) {
Expand All @@ -266,11 +280,10 @@ class CodeCacheArray {
CodeCache *_libs[MAX_NATIVE_LIBS];
volatile int _reserved; // next slot to reserve (CAS by writers)
volatile int _count; // published count (all indices < _count have non-NULL pointers)
volatile size_t _used_memory;
bool _overflow_reported;

public:
CodeCacheArray() : _reserved(0), _count(0), _used_memory(0), _overflow_reported(false) {
CodeCacheArray() : _reserved(0), _count(0), _overflow_reported(false) {
memset(_libs, 0, MAX_NATIVE_LIBS * sizeof(CodeCache *));
}

Expand All @@ -296,7 +309,6 @@ class CodeCacheArray {
} while (!__atomic_compare_exchange_n(&_reserved, &slot, slot + 1,
true, __ATOMIC_RELAXED, __ATOMIC_RELAXED));
assert(__atomic_load_n(&_libs[slot], __ATOMIC_RELAXED) == nullptr);
__atomic_fetch_add(&_used_memory, lib->memoryUsage(), __ATOMIC_RELAXED);
// Store pointer before publishing count. The RELEASE here pairs with
// the ACQUIRE load in operator[]/at() and count().
__atomic_store_n(&_libs[slot], lib, __ATOMIC_RELEASE);
Expand All @@ -316,8 +328,21 @@ class CodeCacheArray {
return __atomic_load_n(&_libs[index], __ATOMIC_ACQUIRE);
}

// Sum the live memory of all registered libraries. Recomputed on demand
// (called only at dump time) so it reflects each library's current size,
// including symbols added after the library was registered. The array is
// append-only, so iterating the published prefix is safe alongside
// concurrent add()s.
size_t memoryUsage() const {
return __atomic_load_n(&_used_memory, __ATOMIC_RELAXED);
size_t total = 0;
int n = count();
for (int i = 0; i < n; i++) {
CodeCache *lib = at(i);
if (lib != nullptr) {
total += (size_t)lib->memoryUsage();
}
}
return total;
}
};

Expand Down
48 changes: 48 additions & 0 deletions ddprof-lib/src/test/cpp/codeCache_ut.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2026, Datadog, Inc.
* SPDX-License-Identifier: Apache-2.0
*/

#include <gtest/gtest.h>
#include "codeCache.h"

Comment thread
Copilot marked this conversation as resolved.
// memoryUsage() must reflect the actual per-symbol name length, not a fixed
// per-symbol constant: adding one symbol grows the reported usage by exactly
// the allocation size of its name string.
TEST(CodeCacheTest, MemoryUsageCountsSymbolNameLength) {
CodeCache cc("testlib");
long long base = cc.memoryUsage();

const char *name =
"a_long_symbol_name_well_beyond_sizeof_NativeFunc_padding_xxxxxxxxxxxx";
cc.add(reinterpret_cast<const void *>(0x1000), 16, name);

EXPECT_EQ(NativeFunc::allocSize(name), (size_t)(cc.memoryUsage() - base));
}

// A longer name costs more than a shorter one — the reported size scales with
// the actual symbol-name length.
TEST(CodeCacheTest, MemoryUsageGrowsWithNameLength) {
CodeCache shortc("lib");
CodeCache longc("lib");
long long short_base = shortc.memoryUsage();
long long long_base = longc.memoryUsage();

shortc.add(reinterpret_cast<const void *>(0x1000), 8, "f");
longc.add(reinterpret_cast<const void *>(0x1000), 8,
"an_extremely_long_symbol_name_padded_out_further_and_further_1234");

EXPECT_GT(longc.memoryUsage() - long_base, shortc.memoryUsage() - short_base);
}

// The blob array is counted at the full CodeBlob size (a CodeBlob is several
// pointers wide): an empty cache already accounts for
// capacity * sizeof(CodeBlob) plus its own name.
TEST(CodeCacheTest, MemoryUsageCountsFullBlobArray) {
CodeCache cc("lib");
long long usage = cc.memoryUsage();
EXPECT_GT(usage, (long long)(INITIAL_CODE_CACHE_CAPACITY * sizeof(CodeBlob)));
// Sanity: a pointer-sized count would be far smaller than the real array.
EXPECT_GT((long long)(INITIAL_CODE_CACHE_CAPACITY * sizeof(CodeBlob)),
(long long)(INITIAL_CODE_CACHE_CAPACITY * sizeof(CodeBlob *)));
}
Loading