Skip to content

Commit f5908e3

Browse files
committed
Improve performance using Unsafe to activate/deactivate thread filter
1 parent 937cb96 commit f5908e3

5 files changed

Lines changed: 152 additions & 26 deletions

File tree

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,3 +406,24 @@ Java_com_datadoghq_profiler_JVMAccess_healthCheck0(JNIEnv *env,
406406
jobject unused) {
407407
return true;
408408
}
409+
410+
extern "C" DLLEXPORT jlong JNICALL
411+
Java_com_datadoghq_profiler_ActiveBitmap_bitmapAddressFor0(JNIEnv *env,
412+
jclass unused,
413+
jint tid) {
414+
u64* bitmap = Profiler::instance()->threadFilter()->bitmapAddressFor((int)tid);
415+
return (jlong)bitmap;
416+
}
417+
418+
extern "C" DLLEXPORT jboolean JNICALL
419+
Java_com_datadoghq_profiler_ActiveBitmap_isActive0(JNIEnv *env,
420+
jclass unused,
421+
jint tid) {
422+
return Profiler::instance()->threadFilter()->accept((int)tid) ? JNI_TRUE : JNI_FALSE;
423+
}
424+
425+
extern "C" DLLEXPORT jlong JNICALL
426+
Java_com_datadoghq_profiler_ActiveBitmap_getActiveCountAddr0(JNIEnv *env,
427+
jclass unused) {
428+
return (jlong)Profiler::instance()->threadFilter()->addressOfSize();
429+
}

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

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "counters.h"
1919
#include "os.h"
2020
#include "reverse_bits.h"
21+
#include <cassert>
2122
#include <stdlib.h>
2223
#include <string.h>
2324

@@ -96,27 +97,43 @@ int ThreadFilter::mapThreadId(int thread_id) {
9697
return tid;
9798
}
9899

99-
bool ThreadFilter::accept(int thread_id) {
100-
thread_id = mapThreadId(thread_id);
101-
u64 *b = bitmap(thread_id);
102-
return b != NULL && (word(b, thread_id) & (1ULL << (thread_id & 0x3f)));
103-
}
104-
105-
void ThreadFilter::add(int thread_id) {
106-
thread_id = mapThreadId(thread_id);
107-
u64 *b = bitmap(thread_id);
100+
// Get bitmap that contains the thread id, create one if it does not exist
101+
u64* ThreadFilter::getBitmapFor(int thread_id) {
102+
int index = thread_id / BITMAP_CAPACITY;
103+
assert(index >= 0 && index < (int)_max_bitmaps);
104+
u64* b = _bitmap[index];
108105
if (b == NULL) {
109106
b = (u64 *)OS::safeAlloc(BITMAP_SIZE);
110107
u64 *oldb = __sync_val_compare_and_swap(
111-
&_bitmap[(u32)thread_id / BITMAP_CAPACITY], NULL, b);
108+
&_bitmap[index], NULL, b);
112109
if (oldb != NULL) {
113110
OS::safeFree(b, BITMAP_SIZE);
114111
b = oldb;
115112
} else {
116113
trackPage();
117114
}
118115
}
116+
return b;
117+
}
119118

119+
u64* ThreadFilter::bitmapAddressFor(int thread_id) {
120+
u64* b = getBitmapFor(thread_id);
121+
thread_id = mapThreadId(thread_id);
122+
assert(b == bitmap(thread_id));
123+
return wordAddress(b, thread_id);
124+
}
125+
126+
bool ThreadFilter::accept(int thread_id) {
127+
thread_id = mapThreadId(thread_id);
128+
u64 *b = bitmap(thread_id);
129+
return b != NULL && (word(b, thread_id) & (1ULL << (thread_id & 0x3f)));
130+
}
131+
132+
void ThreadFilter::add(int thread_id) {
133+
u64 *b = getBitmapFor(thread_id);
134+
assert (b != NULL);
135+
thread_id = mapThreadId(thread_id);
136+
assert(b == bitmap(thread_id));
120137
u64 bit = 1ULL << (thread_id & 0x3f);
121138
if (!(__sync_fetch_and_or(&word(b, thread_id), bit) & bit)) {
122139
atomicInc(_size);

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,21 +51,29 @@ class ThreadFilter {
5151
// todo: add thread safe APIs
5252
return bitmap[((u32)thread_id % BITMAP_CAPACITY) >> 6];
5353
}
54+
55+
u64* const wordAddress(u64 *bitmap, int thread_id) const {
56+
return &bitmap[((u32)thread_id % BITMAP_CAPACITY) >> 6];
57+
}
58+
59+
u64* getBitmapFor(int thread_id);
5460
public:
5561
ThreadFilter();
5662
ThreadFilter(ThreadFilter &threadFilter) = delete;
5763
~ThreadFilter();
5864

59-
bool enabled() { return _enabled; }
65+
bool enabled() const { return _enabled; }
6066

61-
int size() { return _size; }
67+
int size() const { return _size; }
68+
const volatile int* addressOfSize() const { return &_size; }
6269

6370
void init(const char *filter);
6471
void clear();
6572

6673
bool accept(int thread_id);
6774
void add(int thread_id);
6875
void remove(int thread_id);
76+
u64* bitmapAddressFor(int thread_id);
6977

7078
void collect(std::vector<int> &v);
7179
};
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.datadoghq.profiler;
2+
3+
import sun.misc.Unsafe;
4+
import java.lang.reflect.Field;
5+
6+
7+
class ActiveBitmap {
8+
private static final Unsafe UNSAFE = JavaProfiler.UNSAFE;
9+
10+
// Address to size field of ThreadFilter in native
11+
private static long activeCountAddr;
12+
13+
private static final ThreadLocal<Long> Address = new ThreadLocal<Long>() {
14+
@Override protected Long initialValue() {
15+
return -1L;
16+
}
17+
};
18+
19+
public static void initialize() {
20+
activeCountAddr = getActiveCountAddr0();
21+
}
22+
23+
// On native side, we reverse lower 16 bits of thread id when maps to bitmap bit.
24+
// So the active bit position of the specific thread id maps to the reverse order
25+
// of the second lowest byte of thread id.
26+
static long getBitmask(int tid) {
27+
int tmp = (tid >> 8) & 0xff ;
28+
int bits = 0;
29+
for (int index = 0; index <= 7; index++) {
30+
if ((tmp & 0x01) == 0x01) {
31+
bits |= 1 << (7 - index);
32+
}
33+
tmp >>>= 1;
34+
}
35+
return 1L << (bits & 0x3f);
36+
}
37+
38+
static void setActive(int tid, boolean active) {
39+
long addr = Address.get();
40+
if (addr == -1) {
41+
addr = bitmapAddressFor0(tid);
42+
Address.set(addr);
43+
}
44+
long bitmask = getBitmask(tid);
45+
long value = UNSAFE.getLong(addr);
46+
long newVal = active ? (value | bitmask) : (value & ~bitmask);
47+
48+
while (!UNSAFE.compareAndSwapLong(null, addr, value, newVal)) {
49+
value = UNSAFE.getLong(addr);
50+
newVal = active ? (value | bitmask) : (value & ~bitmask);
51+
}
52+
int delta = active ? 1 : -1;
53+
assert activeCountAddr != 0;
54+
UNSAFE.getAndAddInt(null, activeCountAddr, delta);
55+
if (isActive0(tid) != active) {
56+
throw new RuntimeException("SetActive Failed");
57+
}
58+
59+
assert isActive0(tid) == active;
60+
}
61+
62+
63+
// Address of bitmap word that contains the active bit of this thread Id
64+
private static native long bitmapAddressFor0(int tid);
65+
66+
private static native long getActiveCountAddr0();
67+
68+
// For validation
69+
private static native boolean isActive0(int tid);
70+
}
71+

ddprof-lib/src/main/java/com/datadoghq/profiler/JavaProfiler.java

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,17 @@
3434
* libjavaProfiler.so.
3535
*/
3636
public final class JavaProfiler {
37-
private static final Unsafe UNSAFE;
37+
static final Unsafe UNSAFE;
38+
static final boolean isJDK8;
3839
static {
3940
Unsafe unsafe = null;
4041
String version = System.getProperty("java.version");
41-
if (version.startsWith("1.8")) {
42-
try {
43-
Field f = Unsafe.class.getDeclaredField("theUnsafe");
44-
f.setAccessible(true);
45-
unsafe = (Unsafe) f.get(null);
46-
} catch (Exception ignore) { }
47-
}
42+
isJDK8 = version.startsWith("1.8");
43+
try {
44+
Field f = Unsafe.class.getDeclaredField("theUnsafe");
45+
f.setAccessible(true);
46+
unsafe = (Unsafe) f.get(null);
47+
} catch (Exception ignore) { }
4848
UNSAFE = unsafe;
4949
}
5050

@@ -108,6 +108,7 @@ public static synchronized JavaProfiler getInstance(String libLocation, String s
108108
throw new IOException("Failed to load Datadog Java profiler library", result.error);
109109
}
110110
init0();
111+
ActiveBitmap.initialize();
111112

112113
profiler.initializeContextStorage();
113114
instance = profiler;
@@ -128,7 +129,7 @@ private void initializeContextStorage() {
128129
if (this.contextStorage == null) {
129130
int maxPages = getMaxContextPages0();
130131
if (maxPages > 0) {
131-
if (UNSAFE != null) {
132+
if (isJDK8) {
132133
contextBaseOffsets = new long[maxPages];
133134
// be sure to choose an illegal address as a sentinel value
134135
Arrays.fill(contextBaseOffsets, Long.MIN_VALUE);
@@ -208,15 +209,23 @@ public boolean recordTraceRoot(long rootSpanId, String endpoint, int sizeLimit)
208209
* 'filter' option must be enabled to use this method.
209210
*/
210211
public void addThread() {
211-
filterThread0(true);
212+
if (UNSAFE != null) {
213+
ActiveBitmap.setActive(TID.get(), true);
214+
} else {
215+
filterThread0(true);
216+
}
212217
}
213218

214219
/**
215220
* Remove the given thread to the set of profiled threads.
216221
* 'filter' option must be enabled to use this method.
217222
*/
218223
public void removeThread() {
219-
filterThread0(false);
224+
if (UNSAFE != null) {
225+
ActiveBitmap.setActive(TID.get(), false);
226+
} else {
227+
filterThread0(false);
228+
}
220229
}
221230

222231

@@ -229,7 +238,7 @@ public void removeThread() {
229238
*/
230239
public void setContext(long spanId, long rootSpanId) {
231240
int tid = TID.get();
232-
if (UNSAFE != null) {
241+
if (isJDK8) {
233242
setContextJDK8(tid, spanId, rootSpanId);
234243
} else {
235244
setContextByteBuffer(tid, spanId, rootSpanId);
@@ -304,7 +313,7 @@ public void clearContext() {
304313
*/
305314
public void setContextValue(int offset, int value) {
306315
int tid = TID.get();
307-
if (UNSAFE != null) {
316+
if (isJDK8) {
308317
setContextJDK8(tid, offset, value);
309318
} else {
310319
setContextByteBuffer(tid, offset, value);
@@ -335,7 +344,7 @@ public void setContextByteBuffer(int tid, int offset, int value) {
335344

336345
void copyTags(int[] snapshot) {
337346
int tid = TID.get();
338-
if (UNSAFE != null) {
347+
if (isJDK8) {
339348
copyTagsJDK8(tid, snapshot);
340349
} else {
341350
copyTagsByteBuffer(tid, snapshot);

0 commit comments

Comments
 (0)