Skip to content

Commit 517f247

Browse files
committed
v0
1 parent c3fe02e commit 517f247

10 files changed

Lines changed: 200 additions & 56 deletions

File tree

ddprof-lib/src/main/cpp/javaApi.cpp

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -133,15 +133,11 @@ Java_com_datadoghq_profiler_JavaProfiler_getSamples(JNIEnv *env,
133133
// We direct corresponding JNI calls to JavaCritical to make sure the parameters/return value
134134
// still compatible in the event of signature changes in the future.
135135
extern "C" DLLEXPORT void JNICALL
136-
JavaCritical_com_datadoghq_profiler_JavaProfiler_filterThreadAdd0() {
136+
JavaCritical_com_datadoghq_profiler_JavaProfiler_filterThreadAdd0(jlong java_tid) {
137137
ProfiledThread *current = ProfiledThread::current();
138138
if (unlikely(current == nullptr)) {
139139
return;
140140
}
141-
int tid = current->tid();
142-
if (unlikely(tid < 0)) {
143-
return;
144-
}
145141
ThreadFilter *thread_filter = Profiler::instance()->threadFilter();
146142
if (unlikely(!thread_filter->enabled())) {
147143
return;
@@ -158,7 +154,7 @@ JavaCritical_com_datadoghq_profiler_JavaProfiler_filterThreadAdd0() {
158154
if (unlikely(slot_id == -1)) {
159155
return; // Failed to register thread
160156
}
161-
thread_filter->add(tid, slot_id);
157+
thread_filter->add(java_tid, slot_id);
162158
}
163159

164160
extern "C" DLLEXPORT void JNICALL
@@ -167,10 +163,6 @@ JavaCritical_com_datadoghq_profiler_JavaProfiler_filterThreadRemove0() {
167163
if (unlikely(current == nullptr)) {
168164
return;
169165
}
170-
int tid = current->tid();
171-
if (unlikely(tid < 0)) {
172-
return;
173-
}
174166
ThreadFilter *thread_filter = Profiler::instance()->threadFilter();
175167
if (unlikely(!thread_filter->enabled())) {
176168
return;
@@ -187,8 +179,9 @@ JavaCritical_com_datadoghq_profiler_JavaProfiler_filterThreadRemove0() {
187179

188180
extern "C" DLLEXPORT void JNICALL
189181
Java_com_datadoghq_profiler_JavaProfiler_filterThreadAdd0(JNIEnv *env,
190-
jclass unused) {
191-
JavaCritical_com_datadoghq_profiler_JavaProfiler_filterThreadAdd0();
182+
jclass unused,
183+
jlong java_tid) {
184+
JavaCritical_com_datadoghq_profiler_JavaProfiler_filterThreadAdd0(java_tid);
192185
}
193186

194187
extern "C" DLLEXPORT void JNICALL

ddprof-lib/src/main/cpp/threadFilter.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ bool ThreadFilter::accept(SlotID slot_id) const {
163163
return false;
164164
}
165165

166-
void ThreadFilter::add(int tid, SlotID slot_id) {
166+
void ThreadFilter::add(long tid, SlotID slot_id) {
167167
// PRECONDITION: slot_id must be from registerThread() or negative
168168
// Undefined behavior for invalid positive slot_ids (performance optimization)
169169
if (slot_id < 0) return;

ddprof-lib/src/main/cpp/threadFilter.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class ThreadFilter {
4545
bool enabled() const;
4646
// Hot path methods - slot_id MUST be from registerThread(), undefined behavior otherwise
4747
bool accept(SlotID slot_id) const;
48-
void add(int tid, SlotID slot_id);
48+
void add(long tid, SlotID slot_id);
4949
void remove(SlotID slot_id);
5050
void collect(std::vector<int>& tids) const;
5151

@@ -55,7 +55,7 @@ class ThreadFilter {
5555
private:
5656
// Optimized slot structure with padding to avoid false sharing
5757
struct alignas(DEFAULT_CACHE_LINE_SIZE) Slot {
58-
std::atomic<int> value{-1};
58+
std::atomic<long> value{-1};
5959
char padding[DEFAULT_CACHE_LINE_SIZE - sizeof(value)]; // Pad to cache line size
6060
};
6161
static_assert(sizeof(Slot) == DEFAULT_CACHE_LINE_SIZE, "Slot must be exactly one cache line");
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright 2026 Datadog, Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include <cassert>
18+
#include "threadState.inline.h"
19+
#include "vmStructs.h"
20+
21+
ThreadStateResolver* ThreadStateResolver::INSTANCE = nullptr;
22+
23+
void ThreadStateResolver::initialize() {
24+
if (VM::isHotspot()) {
25+
INSTANCE = new VMStructsThreadStateResolver();
26+
} else {
27+
INSTANCE = new JNIThreadStateResolver();
28+
}
29+
}
30+
31+
VMStructsThreadStateResolver::VMStructsThreadStateResolver() {
32+
assert(VM::isHotspot());
33+
}
34+
35+
OSThreadState VMStructsThreadStateResolver::resolveThreadState(jthread thread) {
36+
JNIEnv* jni = VM::jni();
37+
VMThread* vm_thread = VMThread::fromJavaThread(jni, thread);
38+
// Thread no longer reachable
39+
if (vm_thread == nullptr) {
40+
return OSThreadState::TERMINATED;
41+
}
42+
43+
int raw_thread_state = vm_thread->state();
44+
bool is_initialized = raw_thread_state >= JVMJavaThreadState::_thread_in_native &&
45+
raw_thread_state < JVMJavaThreadState::_thread_max_state;
46+
if (!is_initialized) {
47+
return OSThreadState::UNKNOWN;
48+
}
49+
50+
OSThreadState state = OSThreadState::UNKNOWN;
51+
OSThreadState os_state = vm_thread->osThreadState();
52+
if (os_state == OSThreadState::UNKNOWN) {
53+
state = OSThreadState::RUNNABLE;
54+
} else {
55+
state = os_state;
56+
}
57+
return state;
58+
}
59+
60+
ExecutionMode VMStructsThreadStateResolver::resolveThreadExecutionMode(jthread thread) {
61+
JNIEnv* jni = VM::jni();
62+
VMThread* vm_thread = VMThread::fromJavaThread(jni, thread);
63+
return getThreadExecutionMode(vm_thread);
64+
}
65+
66+
67+
JNIThreadStateResolver::JNIThreadStateResolver() {
68+
assert(VM::isOpenJ9() || VM::isZing());
69+
70+
JNIEnv* jni = VM::jni();
71+
jclass thread_class = jni->FindClass("java/lang/Thread");
72+
jclass thread_state_class = jni->FindClass("java/lang/Thread$State");
73+
assert(thread_class != nullptr);
74+
assert(thread_state_class != nullptr);
75+
76+
_thread_get_state_id = jni->GetMethodID(thread_class, "getState", "()Ljava/lang/Thread$State;");
77+
assert(_thread_get_state_id != nullptr);
78+
_state_ordinal_id = jni->GetMethodID(thread_state_class, "ordinal", "()I");
79+
assert(_state_ordinal_id != nullptr);
80+
}
81+
82+
OSThreadState JNIThreadStateResolver::resolveThreadState(jthread thread) {
83+
JNIEnv* jni = VM::jni();
84+
jobject state = jni->CallObjectMethod(thread, _thread_get_state_id);
85+
int ordinal = jni->CallIntMethod(state, _state_ordinal_id);
86+
return ordinal2ThreadState(ordinal);
87+
}
88+
89+
ExecutionMode JNIThreadStateResolver::resolveThreadExecutionMode(jthread thread) {
90+
return ExecutionMode::JAVA;
91+
}
92+
93+
OSThreadState JNIThreadStateResolver::ordinal2ThreadState(int ordinal) {
94+
switch(ordinal) {
95+
case 0: return OSThreadState::NEW;
96+
case 1: return OSThreadState::RUNNABLE;
97+
case 2: return OSThreadState::MONITOR_WAIT;
98+
case 3:
99+
case 4: return OSThreadState::OBJECT_WAIT;
100+
case 5: return OSThreadState::TERMINATED;
101+
default: assert(false);
102+
}
103+
return OSThreadState::UNKNOWN;
104+
}

ddprof-lib/src/main/cpp/threadState.h

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,46 @@ static ExecutionMode convertJvmExecutionState(int state) {
6666
* ExecutionMode::JVM for JVM internal threads,
6767
* or the appropriate execution mode for Java threads
6868
*/
69-
class VMThread;
69+
class VMThread;
7070

7171
inline ExecutionMode getThreadExecutionMode();
72+
inline ExecutionMode getThreadExecutionMode(VMThread* thread);
73+
74+
// The helper classes to resolve thread state.
75+
class ThreadStateResolver {
76+
private:
77+
static ThreadStateResolver* INSTANCE;
78+
79+
public:
80+
// Must be called at initialization phase, in single-threaded mode,
81+
// after resolving JVM type
82+
static void initialize();
83+
84+
static ThreadStateResolver* getInstance() { return INSTANCE; }
85+
86+
virtual OSThreadState resolveThreadState(jthread thread) = 0;
87+
virtual ExecutionMode resolveThreadExecutionMode(jthread thread) = 0;
88+
};
89+
90+
// Hotspot JVM uses VMStructs to resolve thread state and execution mode
91+
class VMStructsThreadStateResolver : public ThreadStateResolver {
92+
public:
93+
VMStructsThreadStateResolver();
94+
OSThreadState resolveThreadState(jthread thread);
95+
ExecutionMode resolveThreadExecutionMode(jthread thread);
96+
};
97+
98+
// J9 and Zing use JNI to resolve thread state and execution mode
99+
class JNIThreadStateResolver : public ThreadStateResolver {
100+
private:
101+
jmethodID _thread_get_state_id;
102+
jmethodID _state_ordinal_id;
103+
public:
104+
JNIThreadStateResolver();
105+
OSThreadState resolveThreadState(jthread thread);
106+
ExecutionMode resolveThreadExecutionMode(jthread thread);
107+
private:
108+
static OSThreadState ordinal2ThreadState(int ordinal);
109+
};
72110

73111
#endif // JAVA_PROFILER_LIBRARY_THREAD_STATE_H

ddprof-lib/src/main/cpp/threadState.inline.h

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@
77
#include "thread.h"
88
#include <stdio.h>
99

10-
inline ExecutionMode getThreadExecutionMode() {
11-
VMThread* vm_thread = VMThread::current();
10+
inline ExecutionMode getThreadExecutionMode(VMThread* vm_thread) {
11+
// This is hotspot JVM specific implementation
12+
assert(VM::isHotspot());
13+
1214
// Not a JVM thread - native thread, e.g. thread launched by JNI code
1315
if (vm_thread == nullptr) {
1416
return ExecutionMode::NATIVE;
1517
}
1618

17-
1819
ProfiledThread *prof_thread = ProfiledThread::currentSignalSafe();
1920
bool is_java_thread = prof_thread != nullptr && prof_thread->isJavaThread();
2021

@@ -29,10 +30,15 @@ inline ExecutionMode getThreadExecutionMode() {
2930
return is_java_thread ? convertJvmExecutionState(raw_thread_state)
3031
: ExecutionMode::JVM;
3132
} else {
32-
// It is a JVM internal thread, may or may not be a Java thread,
33+
// It is a JVM internal thread, may or may not be a Java thread,
3334
// e.g. Compiler thread or GC thread, etc
3435
return ExecutionMode::JVM;
3536
}
3637
}
3738

39+
inline ExecutionMode getThreadExecutionMode() {
40+
VMThread* vm_thread = VMThread::current();
41+
return getThreadExecutionMode(vm_thread);
42+
}
43+
3844
#endif // JAVA_PROFILER_LIBRARY_THREAD_STATE_INLINE_H

ddprof-lib/src/main/cpp/vmEntry.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "os.h"
1616
#include "profiler.h"
1717
#include "safeAccess.h"
18+
#include "threadState.h"
1819
#include "vmStructs.h"
1920
#include <dlfcn.h>
2021
#include <stdlib.h>
@@ -375,6 +376,9 @@ bool VM::initProfilerBridge(JavaVM *vm, bool attach) {
375376
return false;
376377
}
377378

379+
// Initialize thread state resolver
380+
ThreadStateResolver::initialize();
381+
378382
CodeCache *lib = openJvmLibrary();
379383
if (lib == nullptr) {
380384
return false;

ddprof-lib/src/main/cpp/wallClock.cpp

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ void WallClockASGCT::initialize(Arguments& args) {
160160
* We have to be extremely careful when accessing thread's data, so it may not be valid.
161161
*/
162162
void WallClockJVMTI::timerLoop() {
163+
164+
TEST_LOG("VMTI::timerLoop()");
165+
163166
// Check for enablement before attaching/dettaching the current thread
164167
if (!isEnabled()) {
165168
return;
@@ -170,6 +173,7 @@ void WallClockJVMTI::timerLoop() {
170173
return;
171174
}
172175

176+
173177
// Notice:
174178
// We want to cache threads that are captured by collectThread(), so that we can
175179
// clean them up in cleanThreadRefs().
@@ -180,6 +184,21 @@ void WallClockJVMTI::timerLoop() {
180184

181185
// Attach to JVM as the first step
182186
VM::attachThread("Datadog Profiler Wallclock Sampler");
187+
188+
jthread self;
189+
jvmtiError error;
190+
191+
if ((error = jvmti->GetCurrentThread(&self)) != JVMTI_ERROR_NONE) {
192+
TEST_LOG("GetCurrentThread failed, %d", (int)error);
193+
return;
194+
}
195+
196+
JNIEnv* jni = VM::jni();
197+
jclass thread_class = jni->FindClass("java/lang/Thread");
198+
assert(thread_class != nullptr);
199+
jmethodID thread_getId = jni->GetMethodID(thread_class, "getId", "()J");
200+
assert(thread_getId != nullptr);
201+
183202
auto collectThreads = [&](std::vector<ThreadEntry>& threads) {
184203
jvmtiEnv* jvmti = VM::jvmti();
185204
if (jvmti == nullptr) {
@@ -191,11 +210,8 @@ void WallClockJVMTI::timerLoop() {
191210
return;
192211
}
193212

194-
JNIEnv* jni = VM::jni();
195-
196213
ThreadFilter* threadFilter = Profiler::instance()->threadFilter();
197214
bool do_filter = threadFilter->enabled();
198-
int self = OS::threadId();
199215

200216
// If filtering is enabled, collect the filtered TIDs first
201217
std::vector<int> filtered_tids;
@@ -207,46 +223,30 @@ void WallClockJVMTI::timerLoop() {
207223

208224
for (int i = 0; i < threads_count; i++) {
209225
jthread thread = threads_ptr[i];
210-
if (thread != nullptr) {
211-
VMThread* nThread = VMThread::fromJavaThread(jni, thread);
212-
if (nThread == nullptr) {
213-
continue;
214-
}
215-
int tid = nThread->osThreadId();
216-
if (tid != self && (!do_filter ||
226+
if (thread == self) continue;
227+
long tid = jni->CallLongMethod(thread, thread_getId);
228+
229+
if (!do_filter ||
217230
// Use binary search to efficiently find if tid is in filtered_tids
218-
std::binary_search(filtered_tids.begin(), filtered_tids.end(), tid))) {
219-
threads.push_back({nThread, thread, tid});
220-
}
231+
std::binary_search(filtered_tids.begin(), filtered_tids.end(), tid)) {
232+
threads.push_back({thread, tid});
221233
}
222234
}
223235
};
224236

237+
ThreadStateResolver* const resolver = ThreadStateResolver::getInstance();
238+
225239
auto sampleThreads = [&](ThreadEntry& thread_entry, int& num_failures, int& threads_already_exited, int& permission_denied) {
226240
static jint max_stack_depth = (jint)Profiler::instance()->max_stack_depth();
227241

228242
ExecutionEvent event;
229-
VMThread* vm_thread = thread_entry.native;
230-
int raw_thread_state = vm_thread->state();
231-
bool is_initialized = raw_thread_state >= JVMJavaThreadState::_thread_in_native &&
232-
raw_thread_state < JVMJavaThreadState::_thread_max_state;
233-
OSThreadState state = OSThreadState::UNKNOWN;
234-
ExecutionMode mode = ExecutionMode::UNKNOWN;
235-
if (vm_thread == nullptr || !is_initialized) {
243+
jthread thread = thread_entry.java;
244+
245+
event._thread_state = resolver->resolveThreadState(thread);
246+
if (event._thread_state == OSThreadState::TERMINATED) {
236247
return false;
237248
}
238-
OSThreadState os_state = vm_thread->osThreadState();
239-
if (os_state == OSThreadState::TERMINATED) {
240-
return false;
241-
} else if (os_state == OSThreadState::UNKNOWN) {
242-
state = OSThreadState::RUNNABLE;
243-
} else {
244-
state = os_state;
245-
}
246-
mode = getThreadExecutionMode();
247-
248-
event._thread_state = state;
249-
event._execution_mode = mode;
249+
event._execution_mode = resolver->resolveThreadExecutionMode(thread);
250250
event._weight = 1;
251251

252252
Profiler::instance()->recordJVMTISample(1, thread_entry.tid, thread_entry.java, BCI_WALL, &event, false);

0 commit comments

Comments
 (0)