Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
8308934
Implement option to use JVMTI/JFR stack-walker for CPU- and wall-cloc…
rkennke Apr 24, 2026
045bf63
MacOS parts
rkennke Apr 27, 2026
57ab35f
Extract JVMTI stack-walker feature detection into separate method
rkennke Apr 28, 2026
8985fc1
Fix jvmtistacks initialization when enabled via runtime execute()
rkennke May 4, 2026
d67e0ca
Fix ITimerJvmti::check() to probe ITIMER_PROF support
rkennke May 4, 2026
a7ee53b
Use explicit Error::ok() check instead of implicit bool conversion
rkennke May 4, 2026
203f572
Revert "Use explicit Error::ok() check instead of implicit bool conve…
rkennke May 5, 2026
14ae52a
Fix data race on _request_stack_trace / _request_stack_trace_initialized
rkennke May 5, 2026
d96f061
Fix _sample_seq atomicity and false sharing in recordSampleDelegated
rkennke May 5, 2026
cd61a8a
Fix misleading comment in selectWallEngine
rkennke May 5, 2026
5c49685
Save/restore errno in WallClockJvmti::signalHandler
rkennke May 5, 2026
dc93cdf
Merge origin/main
rkennke May 5, 2026
ddd62a8
Add signal origin check to CTimerJvmti and eliminate start() duplication
rkennke May 5, 2026
c084dfd
Fix CTimer::start() swallowing thread-registration failures
rkennke May 5, 2026
028af98
Free all sub-allocations from GetExtensionFunctions
rkennke May 5, 2026
41d7839
Fix jvmtistacks argument parsing to reject unrecognized values
rkennke May 5, 2026
ab9a4e1
Track and surface lock-contention drops in recordSampleDelegated
rkennke May 5, 2026
17dbc3e
Change recordSampleDelegated return type to void
rkennke May 5, 2026
ad24fe8
Fix stale jdk.AsyncStackTrace references to jdk.StackTraceRequest
rkennke May 5, 2026
f9dcbbe
Document VM::canRequestStackTrace() precondition in WallClockJvmti::i…
rkennke May 5, 2026
8adaf2b
Don't reset _sample_seq between recording sessions
rkennke May 5, 2026
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
15 changes: 15 additions & 0 deletions ddprof-lib/src/main/cpp/arguments.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,21 @@ Error Arguments::parse(const char *args) {
_remote_symbolication = true;
}

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

CASE("wallsampler")
if (value != NULL) {
switch (value[0]) {
Expand Down
4 changes: 3 additions & 1 deletion ddprof-lib/src/main/cpp/arguments.h
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ class Arguments {
bool _lightweight;
bool _enable_method_cleanup;
bool _remote_symbolication; // Enable remote symbolication for native frames
bool _jvmtistacks; // Delegate CPU/wall stack walks to HotSpot JFR RequestStackTrace extension

Arguments(bool persistent = false)
: _buf(NULL),
Expand Down Expand Up @@ -227,7 +228,8 @@ class Arguments {
_context_attributes({}),
_lightweight(false),
_enable_method_cleanup(true),
_remote_symbolication(false) {}
_remote_symbolication(false),
_jvmtistacks(false) {}

~Arguments();

Expand Down
16 changes: 11 additions & 5 deletions ddprof-lib/src/main/cpp/counters.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,17 @@
X(WALKVM_CONT_ENTRY_NULL, "walkvm_cont_entry_null") \
X(NATIVE_LIBS_DROPPED, "native_libs_dropped") \
X(SIGACTION_PATCHED_LIBS, "sigaction_patched_libs") \
X(SIGACTION_INTERCEPTED, "sigaction_intercepted") \
X(CTIMER_SIGNAL_OWN, "ctimer_signal_own") \
X(CTIMER_SIGNAL_FOREIGN, "ctimer_signal_foreign") \
X(WALLCLOCK_SIGNAL_OWN, "wallclock_signal_own") \
X(WALLCLOCK_SIGNAL_FOREIGN, "wallclock_signal_foreign")
X(SIGACTION_INTERCEPTED, "sigaction_intercepted") \
X(CTIMER_SIGNAL_OWN, "ctimer_signal_own") \
X(CTIMER_SIGNAL_FOREIGN, "ctimer_signal_foreign") \
X(WALLCLOCK_SIGNAL_OWN, "wallclock_signal_own") \
X(WALLCLOCK_SIGNAL_FOREIGN, "wallclock_signal_foreign") \
X(JVMTI_STACKS_INIT_OK, "jvmti_stacks_init_ok") \
X(JVMTI_STACKS_INIT_FAILED, "jvmti_stacks_init_failed") \
X(JVMTI_STACKS_REQUESTED, "jvmti_stacks_requested") \
X(JVMTI_STACKS_FAILED_WRONG_PHASE, "jvmti_stacks_failed_wrong_phase") \
X(JVMTI_STACKS_FAILED_OTHER, "jvmti_stacks_failed_other") \
X(JVMTI_STACKS_DROPPED_LOCK, "jvmti_stacks_dropped_lock")
#define X_ENUM(a, b) a,
typedef enum CounterId : int {
DD_COUNTER_TABLE(X_ENUM) DD_NUM_COUNTERS
Expand Down
34 changes: 33 additions & 1 deletion ddprof-lib/src/main/cpp/ctimer.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
#include <signal.h>

class CTimer : public Engine {
private:
protected:
// This is accessed from signal handlers, so must be async-signal-safe
static bool _enabled;
static long _interval;
Expand All @@ -38,6 +38,7 @@ class CTimer : public Engine {
int registerThread(int tid);
void unregisterThread(int tid);

private:
// cppcheck-suppress unusedPrivateFunction
static void signalHandler(int signo, siginfo_t *siginfo, void *ucontext);

Expand All @@ -60,6 +61,24 @@ class CTimer : public Engine {
static int getSignal() { return _signal; }
};

// A CPU-time engine that reuses CTimer's per-thread timer_create / SIGPROF
// dispatch, but instead of walking the stack in the signal handler delegates
// the walk to HotSpot's JFR RequestStackTrace JVMTI extension. The sampled
// event is emitted on our side with only a correlation ID; the JVM writes
// the stack trace (and its own JFR stack-trace id) into the concurrent JFR
// recording as jdk.StackTraceRequest. See VM::canRequestStackTrace().
class CTimerJvmti : public CTimer {
private:
// cppcheck-suppress unusedPrivateFunction
static void signalHandler(int signo, siginfo_t *siginfo, void *ucontext);

public:
const char *name() { return "CTimerJvmti"; }

Error check(Arguments &args);
Error start(Arguments &args);
};

#else

class CTimer : public Engine {
Expand All @@ -75,6 +94,19 @@ class CTimer : public Engine {
static bool supported() { return false; }
};

class CTimerJvmti : public Engine {
public:
const char *name() { return "CTimerJvmti"; }

Error check(Arguments &args) {
return Error("CTimerJvmti is not supported on this platform");
}

Error start(Arguments &args) {
return Error("CTimerJvmti is not supported on this platform");
}
};

#endif // __linux__

#endif // _CTIMER_H
67 changes: 66 additions & 1 deletion ddprof-lib/src/main/cpp/ctimer_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ Error CTimer::start(Arguments &args) {
}
delete thread_list;

return Error::OK;
return result;
}

void CTimer::stop() {
Expand All @@ -184,6 +184,71 @@ void CTimer::stop() {
}
}

Error CTimerJvmti::check(Arguments &args) {
if (!VM::canRequestStackTrace()) {
return Error("HotSpot RequestStackTrace JVMTI extension not available");
}
return CTimer::check(args);
}

Error CTimerJvmti::start(Arguments &args) {
if (!VM::canRequestStackTrace()) {
return Error("HotSpot RequestStackTrace JVMTI extension not available");
}
Error result = CTimer::start(args);
if (result) return result;
// Override the signal handler installed by CTimer::start with our own,
// which delegates stack walking to the HotSpot JFR extension.
OS::installSignalHandler(_signal, CTimerJvmti::signalHandler);
return Error::OK;
}

void CTimerJvmti::signalHandler(int signo, siginfo_t *siginfo, void *ucontext) {
if (!OS::shouldProcessSignal(siginfo, SI_TIMER, SignalCookie::cpu())) {
Counters::increment(CTIMER_SIGNAL_FOREIGN);
OS::forwardForeignSignal(signo, siginfo, ucontext);
return;
}
Counters::increment(CTIMER_SIGNAL_OWN);

CriticalSection cs;
if (!cs.entered()) {
return;
}
int saved_errno = errno;
if (!__atomic_load_n(&_enabled, __ATOMIC_ACQUIRE)) {
errno = saved_errno;
return;
}
int tid = 0;
ProfiledThread *current = ProfiledThread::currentSignalSafe();
assert(current == nullptr || !current->isDeepCrashHandler());
if (current != nullptr && JVMThread::isInitialized() && JVMThread::current() == nullptr
&& current->inInitWindow()) {
current->tickInitWindow();
errno = saved_errno;
return;
}
if (current != NULL) {
current->noteCPUSample(Profiler::instance()->recordingEpoch());
tid = current->tid();
} else {
tid = OS::threadId();
}
Shims::instance().setSighandlerTid(tid);

ExecutionEvent event;
event._execution_mode = getThreadExecutionMode();
// Opted into JVMTI delegation; drop the sample if the JVM rejects the
// request (WRONG_PHASE if JFR is not recording, NOT_AVAILABLE if
// jdk.StackTraceRequest is disabled). recordSampleDelegated() bumps the
// failure counters; there is no fallback to ASGCT in this engine.
Profiler::instance()->recordSampleDelegated(ucontext, _interval, tid,
BCI_CPU, &event);
Shims::instance().setSighandlerTid(-1);
errno = saved_errno;
}

void CTimer::signalHandler(int signo, siginfo_t *siginfo, void *ucontext) {
// Reject signals that did not originate from our timer_create timers.
// This guards against Go's process-wide setitimer(ITIMER_PROF) and other
Expand Down
35 changes: 33 additions & 2 deletions ddprof-lib/src/main/cpp/flightRecorder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1496,6 +1496,7 @@ void Recording::writeEventSizePrefix(Buffer *buf, int start) {
}

void Recording::recordExecutionSample(Buffer *buf, int tid, u64 call_trace_id,
u64 correlation_id,
ExecutionEvent *event) {
int start = buf->skip(1);
buf->putVar64(T_EXECUTION_SAMPLE);
Expand All @@ -1505,12 +1506,14 @@ void Recording::recordExecutionSample(Buffer *buf, int tid, u64 call_trace_id,
buf->put8(static_cast<int>(event->_thread_state));
buf->put8(static_cast<int>(event->_execution_mode));
buf->putVar64(event->_weight);
buf->putVar64(correlation_id);
writeCurrentContext(buf);
writeEventSizePrefix(buf, start);
flushIfNeeded(buf);
}

void Recording::recordMethodSample(Buffer *buf, int tid, u64 call_trace_id,
u64 correlation_id,
ExecutionEvent *event) {
int start = buf->skip(1);
buf->putVar64(T_METHOD_SAMPLE);
Expand All @@ -1520,6 +1523,7 @@ void Recording::recordMethodSample(Buffer *buf, int tid, u64 call_trace_id,
buf->put8(static_cast<int>(event->_thread_state));
buf->put8(static_cast<int>(event->_execution_mode));
buf->putVar64(event->_weight);
buf->putVar64(correlation_id);
writeCurrentContext(buf);
writeEventSizePrefix(buf, start);
flushIfNeeded(buf);
Expand Down Expand Up @@ -1822,11 +1826,11 @@ void FlightRecorder::recordEvent(int lock_index, int tid, u64 call_trace_id,
RecordingBuffer *buf = rec->buffer(lock_index);
switch (event_type) {
case BCI_CPU:
rec->recordExecutionSample(buf, tid, call_trace_id,
rec->recordExecutionSample(buf, tid, call_trace_id, 0,
(ExecutionEvent *)event);
break;
case BCI_WALL:
rec->recordMethodSample(buf, tid, call_trace_id,
rec->recordMethodSample(buf, tid, call_trace_id, 0,
(ExecutionEvent *)event);
break;
case BCI_ALLOC:
Expand All @@ -1852,6 +1856,33 @@ void FlightRecorder::recordEvent(int lock_index, int tid, u64 call_trace_id,
}
}

void FlightRecorder::recordEventDelegated(int lock_index, int tid,
u64 correlation_id, int event_type,
Event *event) {
OptionalSharedLockGuard locker(&_rec_lock);
if (locker.ownsLock()) {
Recording* rec = _rec;
if (rec != nullptr) {
RecordingBuffer *buf = rec->buffer(lock_index);
switch (event_type) {
case BCI_CPU:
rec->recordExecutionSample(buf, tid, 0, correlation_id,
(ExecutionEvent *)event);
break;
case BCI_WALL:
rec->recordMethodSample(buf, tid, 0, correlation_id,
(ExecutionEvent *)event);
break;
default:
// Delegation is only wired for CPU/wall samples in v1.
return;
}
rec->flushIfNeeded(buf);
rec->addThread(lock_index, tid);
}
}
}

void FlightRecorder::recordLog(LogLevel level, const char *message,
size_t len) {
OptionalSharedLockGuard locker(&_rec_lock);
Expand Down
11 changes: 9 additions & 2 deletions ddprof-lib/src/main/cpp/flightRecorder.h
Original file line number Diff line number Diff line change
Expand Up @@ -273,9 +273,9 @@ class Recording {
void writeCurrentContext(Buffer *buf);

void recordExecutionSample(Buffer *buf, int tid, u64 call_trace_id,
ExecutionEvent *event);
u64 correlation_id, ExecutionEvent *event);
void recordMethodSample(Buffer *buf, int tid, u64 call_trace_id,
ExecutionEvent *event);
u64 correlation_id, ExecutionEvent *event);
void recordWallClockEpoch(Buffer *buf, WallClockEpochEvent *event);
void recordTraceRoot(Buffer *buf, int tid, TraceRootEvent *event);
void recordQueueTime(Buffer *buf, int tid, QueueTimeEvent *event);
Expand Down Expand Up @@ -353,6 +353,13 @@ class FlightRecorder {
void recordEvent(int lock_index, int tid, u64 call_trace_id, int event_type,
Event *event);

// Emit a BCI_CPU / BCI_WALL sample with no stack-trace attached to our
// recording. `correlation_id` is the same jlong passed to the HotSpot
// RequestStackTrace extension so downstream tooling can join our event with
// the JVM-emitted jdk.StackTraceRequest.
void recordEventDelegated(int lock_index, int tid, u64 correlation_id,
int event_type, Event *event);

void recordLog(LogLevel level, const char *message, size_t len);

void recordDatadogSetting(int lock_index, int length, const char *name,
Expand Down
2 changes: 1 addition & 1 deletion ddprof-lib/src/main/cpp/hotspot/vmStructs.h
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ typedef void* address;
field(_flag_type_offset, offset, MATCH_SYMBOLS("_type", "type")) \
type_end() \
type_begin(VMOop, MATCH_SYMBOLS("oopDesc")) \
field(_oop_klass_offset, offset, MATCH_SYMBOLS("_metadata._klass")) \
field(_oop_klass_offset, offset, MATCH_SYMBOLS("_metadata._klass", "_compressed_klass")) \
Comment thread
jbachorik marked this conversation as resolved.
type_end() \
type_begin(VMUniverse, MATCH_SYMBOLS("Universe", "CompressedKlassPointers")) \
field(_narrow_klass_base_addr, address, MATCH_SYMBOLS("_narrow_klass._base", "_base")) \
Expand Down
Loading
Loading