Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
12 changes: 8 additions & 4 deletions .claude/commands/build-and-summarize.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# build-and-summarize

Runs `./gradlew` with full output captured to a timestamped log, shows minimal live progress (task starts + final build/test summary), then asks the `gradle-logs-analyst` agent to produce structured artifacts from the log.
Execute the bash script `~/.claude/commands/build-and-summarize` with all provided arguments.

## Usage
```bash
./.claude/commands/build-and-summarize [<gradle-args>...]
This script will:
- Run `./gradlew` with the specified arguments (defaults to 'build' if none provided)
- Capture full output to a timestamped log in `build/logs/`
- Show minimal live progress in the console
- Delegate to the `gradle-logs-analyst` agent for structured analysis

Pass through all arguments exactly as provided by the user.
60 changes: 58 additions & 2 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,65 @@
"WebFetch(domain:github.com)",
"WebFetch(domain:raw.githubusercontent.com)",
"WebFetch(domain:raw.githubusercontent.com)",
"Bash(./.claude/commands/build-and-summarize:*)"
"Bash(./.claude/commands/build-and-summarize:*)",
"Bash(jps:*)",
"Bash(cat:*)",
"Bash(git stash:*)",
"Bash(find:*)",
"Bash(jfr print:*)",
"Bash(JFR_SHELL=\"$HOME/opensource/jafar/jfr-shell/build/jlink/bin/jfr-shell\")",
"Bash($JFR_SHELL metadata /tmp/recordings/dwarf/RemoteSymbolicationTest_testRemoteSymbolicationEnabled8759671988111138461.jfr --events-only)",
"Bash($HOME/opensource/jafar/run-jfr-shell.sh:*)",
"Bash(jfr-shell metadata:*)",
"Bash(jbang app install:*)",
"Bash(source ~/.bashrc)",
"Skill(jfr_analysis)",
"Bash(jbang trust add:*)",
"Bash(jfr-shell show:*)",
"SlashCommand(/build-and-summarize:*)",
"Bash(readelf:*)",
"Bash(strings:*)",
"Bash(xargs strings:*)",
"Bash(xxd:*)",
"Bash(xargs cat:*)",
"Bash(xargs jfr print --events datadog.ExecutionSample)",
"WebSearch",
"WebFetch(domain:mvnrepository.com)",
"WebFetch(domain:central.sonatype.com)",
"Bash(jfr-shell:*)",
"Bash(java -cp:*)",
"Bash(git commit:*)",
"Bash(git restore:*)",
"Bash(xargs -I {} bash -c 'echo \"\"\"\"{}\"\"\"\"; {}/bin/java -version 2>&1 | head -3')",
"Bash(JAVA_TEST_HOME=/usr/local/sdkman/candidates/java/25.0.1-tem ./.claude/commands/build-and-summarize:*)",
"Bash(/usr/local/sdkman/candidates/java/17.0.17-sem/bin/java:*)",
"Bash(JAVA_TEST_HOME=/usr/local/sdkman/candidates/java/17.0.17-sem ./.claude/commands/build-and-summarize:*)",
"Bash(JAVA_TEST_HOME=/usr/local/sdkman/candidates/java/25.0.1-sem ./.claude/commands/build-and-summarize:*)",
"Bash(java -version:*)",
"WebFetch(domain:eclipse.dev)",
"WebFetch(domain:eclipse-openj9.github.io)",
"Bash(git log:*)",
"Bash(git merge-base:*)",
"Bash(xargs git log --oneline -1)",
"Bash(~/.claude/commands/build-and-summarize spotlessApply)",
"Bash(gh api:*)",
"Bash(git grep:*)",
"Bash(git --no-pager log --oneline -5)",
"Bash(command -v:*)",
"Bash(jj status:*)",
"Bash(jj git init:*)",
"Bash(jj commit:*)",
"Bash(jj log:*)",
"Bash(jj diff:*)",
"Bash(jj bookmark set:*)",
"Bash(jj git push:*)",
"Bash(jj bookmark:*)",
"Bash(jj config set:*)",
"Bash(jj describe:*)",
"Bash(jj metaedit:*)",
"Bash(gh pr list:*)"
],
"deny": [],
"ask": []
}
}
}
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,25 @@ Improved thread-local storage initialization to prevent race conditions:

These architectural improvements focus on eliminating race conditions, improving performance in high-throughput scenarios, and providing better debugging capabilities for the native profiling engine.

### Remote Symbolication Support (2025)

Added support for remote symbolication to enable offloading symbol resolution from the agent to backend services:

- **Build-ID extraction**: Automatically extracts GNU build-id from ELF binaries on Linux
- **Raw addressing information**: Stores build-id and PC offset instead of resolved symbol names
- **Remote symbolication mode**: Enable with `remotesym=true` profiler argument
- **JFR integration**: Remote frames serialized with build-id and offset for backend resolution
- **Zero encoding overhead**: Uses dedicated frame type (FRAME_NATIVE_REMOTE) for efficient serialization

**Benefits**:
- Reduces agent overhead by eliminating local symbol resolution
- Enables centralized symbol resolution with better caching
- Supports scenarios where debug symbols are not available locally

**Key files**: `symbols_linux_dd.h`, `symbols_linux_dd.cpp`, `profiler.cpp`, `flightRecorder.cpp`

For detailed documentation, see [doc/REMOTE_SYMBOLICATION.md](doc/REMOTE_SYMBOLICATION.md).

## Contributing
1. Fork the repository
2. Create a feature branch
Expand Down
40 changes: 28 additions & 12 deletions ddprof-lib/src/main/cpp/arguments.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,10 @@ static const Multiplier UNIVERSAL[] = {
// samples
// generations - track surviving generations
// lightweight[=BOOL] - enable lightweight profiling - events without
// stacktraces (default: true) jfr - dump events in Java
// stacktraces (default: true)
// remotesym[=BOOL] - enable remote symbolication for native frames
// (stores build-id and PC offset instead of symbol names)
// jfr - dump events in Java
// Flight Recorder format interval=N - sampling interval in ns
// (default: 10'000'000, i.e. 10 ms) jstackdepth=N - maximum Java stack
// depth (default: 2048) safemode=BITS - disable stack recovery
Expand Down Expand Up @@ -319,17 +322,30 @@ Error Arguments::parse(const char *args) {
_lightweight = false;
}
}
CASE("wallsampler")
if (value != NULL) {
switch (value[0]) {
case 'j':
_wallclock_sampler = JVMTI;
break;
case 'a':
default:
_wallclock_sampler = ASGCT;
}
}

CASE("remotesym")
if (value != NULL) {
switch (value[0]) {
case 'y': // yes
case 't': // true
_remote_symbolication = true;
break;
default:
_remote_symbolication = false;
}
}

CASE("wallsampler")
if (value != NULL) {
switch (value[0]) {
case 'j':
_wallclock_sampler = JVMTI;
break;
case 'a':
default:
_wallclock_sampler = ASGCT;
}
}

DEFAULT()
if (_unknown_arg == NULL)
Expand Down
7 changes: 5 additions & 2 deletions ddprof-lib/src/main/cpp/arguments.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ struct StackWalkFeatures {
unsigned short vtable_target : 1; // show receiver classes of vtable/itable stubs
unsigned short comp_task : 1; // display current compilation task for JIT threads
unsigned short pc_addr : 1; // record exact PC address for each sample
unsigned short _padding : 3; // pad structure to 16 bits
unsigned short remote_sym : 1; // use remote symbolication (store build-id + offset instead of symbol names)
unsigned short _padding : 2; // pad structure to 16 bits
};

struct Multiplier {
Expand Down Expand Up @@ -187,6 +188,7 @@ class Arguments {
int _jfr_options;
std::vector<std::string> _context_attributes;
bool _lightweight;
bool _remote_symbolication; // Enable remote symbolication for native frames

Arguments(bool persistent = false)
: _buf(NULL),
Expand Down Expand Up @@ -219,7 +221,8 @@ class Arguments {
_jfr_options(0),
_context_attributes({}),
_wallclock_sampler(ASGCT),
_lightweight(false) {}
_lightweight(false),
_remote_symbolication(false) {}

~Arguments();

Expand Down
78 changes: 75 additions & 3 deletions ddprof-lib/src/main/cpp/codeCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

#include "codeCache.h"
#include "common.h"
#include "dwarf_dd.h"
#include "os_dd.h"

Expand Down Expand Up @@ -37,6 +38,11 @@ CodeCache::CodeCache(const char *name, short lib_index,
_plt_size = 0;
_debug_symbols = false;

// Initialize build-id fields
_build_id = nullptr;
_build_id_len = 0;
_load_bias = 0;

memset(_imports, 0, sizeof(_imports));
_imports_patchable = imports_patchable;

Expand All @@ -54,10 +60,31 @@ CodeCache::CodeCache(const CodeCache &other) {
_min_address = other._min_address;
_max_address = other._max_address;
_text_base = other._text_base;
_image_base = other._image_base;

_imports_patchable = other._imports_patchable;
_plt_offset = other._plt_offset;
_plt_size = other._plt_size;
_debug_symbols = other._debug_symbols;

// Copy build-id information
if (other._build_id != nullptr && other._build_id_len > 0) {
size_t hex_str_len = strlen(other._build_id);
_build_id = static_cast<char*>(malloc(hex_str_len + 1));
if (_build_id != nullptr) {
strcpy(_build_id, other._build_id);
_build_id_len = other._build_id_len;
} else {
// malloc failed - set consistent state
_build_id_len = 0;
}
} else {
_build_id = nullptr;
_build_id_len = 0;
}
_load_bias = other._load_bias;

memset(_imports, 0, sizeof(_imports));
_imports_patchable = other._imports_patchable;

_dwarf_table_length = other._dwarf_table_length;
_dwarf_table = new FrameDesc[_dwarf_table_length];
Expand All @@ -77,17 +104,39 @@ CodeCache &CodeCache::operator=(const CodeCache &other) {
delete _name;
delete _dwarf_table;
delete _blobs;
free(_build_id); // Free existing build-id

_name = NativeFunc::create(other._name, -1);
_lib_index = other._lib_index;
_min_address = other._min_address;
_max_address = other._max_address;
_text_base = other._text_base;

_imports_patchable = other._imports_patchable;
_image_base = other._image_base;

_plt_offset = other._plt_offset;
_plt_size = other._plt_size;
_debug_symbols = other._debug_symbols;

// Copy build-id information
if (other._build_id != nullptr && other._build_id_len > 0) {
size_t hex_str_len = strlen(other._build_id);
_build_id = static_cast<char*>(malloc(hex_str_len + 1));
if (_build_id != nullptr) {
strcpy(_build_id, other._build_id);
_build_id_len = other._build_id_len;
} else {
// malloc failed - set consistent state
_build_id = nullptr;
_build_id_len = 0;
}
} else {
_build_id = nullptr;
_build_id_len = 0;
}
_load_bias = other._load_bias;

memset(_imports, 0, sizeof(_imports));
_imports_patchable = other._imports_patchable;

_dwarf_table_length = other._dwarf_table_length;
_dwarf_table = new FrameDesc[_dwarf_table_length];
Expand All @@ -110,6 +159,7 @@ CodeCache::~CodeCache() {
NativeFunc::destroy(_name);
delete[] _blobs;
delete _dwarf_table;
free(_build_id); // Free build-id memory
}

void CodeCache::expand() {
Expand Down Expand Up @@ -387,3 +437,25 @@ FrameDesc CodeCache::findFrameDesc(const void *pc) {
return FrameDesc::default_frame;
}
}

void CodeCache::setBuildId(const char* build_id, size_t build_id_len) {
// Free existing build-id if any
free(_build_id);
_build_id = nullptr;
_build_id_len = 0;

if (build_id != nullptr && build_id_len > 0) {
// build_id is a hex string, allocate based on actual string length
size_t hex_str_len = strlen(build_id);
_build_id = static_cast<char*>(malloc(hex_str_len + 1));

if (_build_id != nullptr) {
// Copy the hex string
strcpy(_build_id, build_id);
// Store the original byte length (not hex string length)
_build_id_len = build_id_len;

TEST_LOG("setBuildId: hex='%s', len=%zu", build_id, build_id_len);
}
}
}
21 changes: 20 additions & 1 deletion ddprof-lib/src/main/cpp/codeCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ class CodeCache {
unsigned int _plt_offset;
unsigned int _plt_size;

// Build-ID and load bias for remote symbolication
char *_build_id; // GNU build-id (hex string, null if not available)
size_t _build_id_len; // Build-id length in bytes (not hex string length)
uintptr_t _load_bias; // Load bias (image_base - file_base address)

void **_imports[NUM_IMPORTS][NUM_IMPORT_TYPES];
bool _imports_patchable;
bool _debug_symbols;
Expand Down Expand Up @@ -169,6 +174,19 @@ class CodeCache {

void setDebugSymbols(bool debug_symbols) { _debug_symbols = debug_symbols; }

// Build-ID and remote symbolication support
const char* buildId() const { return _build_id; }
size_t buildIdLen() const { return _build_id_len; }
bool hasBuildId() const { return _build_id != nullptr; }
uintptr_t loadBias() const { return _load_bias; }
short libIndex() const { return _lib_index; }

// Sets the build-id (hex string) and stores the original byte length
// build_id: null-terminated hex string (e.g., "abc123..." for 40-char string)
// build_id_len: original byte length before hex conversion (e.g., 20 bytes)
void setBuildId(const char* build_id, size_t build_id_len);
void setLoadBias(uintptr_t load_bias) { _load_bias = load_bias; }

void add(const void *start, int length, const char *name,
bool update_bounds = false);
void updateBounds(const void *start, const void *end);
Expand Down Expand Up @@ -226,6 +244,7 @@ class CodeCacheArray {
}

CodeCache *operator[](int index) { return _libs[index]; }
Copy link

Copilot AI Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate operator[] definition with different signatures. Line 246 provides non-const access returning a plain pointer, while line 247 provides const access with atomic load. This could lead to confusion about which overload is called in different contexts.

Consider using more distinctive names (e.g., get() and getAtomic()) or ensuring the const-correctness is consistent across both overloads.

Suggested change
CodeCache *operator[](int index) { return _libs[index]; }
CodeCache *operator[](int index) { return __atomic_load_n(&_libs[index], __ATOMIC_ACQUIRE); }

Copilot uses AI. Check for mistakes.
CodeCache *operator[](int index) const { return __atomic_load_n(&_libs[index], __ATOMIC_ACQUIRE); }

int count() const { return __atomic_load_n(&_count, __ATOMIC_RELAXED); }

Expand All @@ -247,7 +266,7 @@ class CodeCacheArray {
return lib;
}

size_t memoryUsage() {
size_t memoryUsage() const {
return __atomic_load_n(&_used_memory, __ATOMIC_RELAXED);
}
};
Expand Down
Loading
Loading