Skip to content

Commit 23c5c12

Browse files
committed
Fill jvm.thread.state attribute for jvm.thread.count metric on jdk8
1 parent ab79c1a commit 23c5c12

File tree

2 files changed

+109
-7
lines changed
  • instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src

2 files changed

+109
-7
lines changed

instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/main/java/io/opentelemetry/instrumentation/runtimemetrics/java8/Threads.java

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,15 @@
1919
import java.lang.management.ManagementFactory;
2020
import java.lang.management.ThreadInfo;
2121
import java.lang.management.ThreadMXBean;
22+
import java.lang.reflect.Method;
2223
import java.util.ArrayList;
2324
import java.util.HashMap;
2425
import java.util.List;
2526
import java.util.Locale;
2627
import java.util.Map;
2728
import java.util.function.Consumer;
29+
import java.util.function.Function;
30+
import java.util.function.Supplier;
2831
import javax.annotation.Nullable;
2932

3033
/**
@@ -55,11 +58,34 @@ public final class Threads {
5558

5659
/** Register observers for java runtime class metrics. */
5760
public static List<AutoCloseable> registerObservers(OpenTelemetry openTelemetry) {
58-
return INSTANCE.registerObservers(openTelemetry, ManagementFactory.getThreadMXBean());
61+
return INSTANCE.registerObservers(openTelemetry, !isJava9OrNewer() && GET_THREADS != null);
62+
}
63+
64+
private List<AutoCloseable> registerObservers(OpenTelemetry openTelemetry, boolean useThread) {
65+
if (useThread) {
66+
return registerObservers(openTelemetry, Threads::getThreads);
67+
}
68+
return registerObservers(openTelemetry, ManagementFactory.getThreadMXBean());
5969
}
6070

6171
// Visible for testing
6272
List<AutoCloseable> registerObservers(OpenTelemetry openTelemetry, ThreadMXBean threadBean) {
73+
return registerObservers(
74+
openTelemetry,
75+
isJava9OrNewer() ? Threads::java9AndNewerCallback : Threads::java8Callback,
76+
threadBean);
77+
}
78+
79+
// Visible for testing
80+
List<AutoCloseable> registerObservers(
81+
OpenTelemetry openTelemetry, Supplier<Thread[]> threadSupplier) {
82+
return registerObservers(openTelemetry, Threads::java8ThreadCallback, threadSupplier);
83+
}
84+
85+
private static <T> List<AutoCloseable> registerObservers(
86+
OpenTelemetry openTelemetry,
87+
Function<T, Consumer<ObservableLongMeasurement>> callbackProvider,
88+
T threadInfo) {
6389
Meter meter = JmxRuntimeMetricsUtil.getMeter(openTelemetry);
6490
List<AutoCloseable> observables = new ArrayList<>();
6591

@@ -68,13 +94,13 @@ List<AutoCloseable> registerObservers(OpenTelemetry openTelemetry, ThreadMXBean
6894
.upDownCounterBuilder("jvm.thread.count")
6995
.setDescription("Number of executing platform threads.")
7096
.setUnit("{thread}")
71-
.buildWithCallback(
72-
isJava9OrNewer() ? java9AndNewerCallback(threadBean) : java8Callback(threadBean)));
97+
.buildWithCallback(callbackProvider.apply(threadInfo)));
7398

7499
return observables;
75100
}
76101

77102
@Nullable private static final MethodHandle THREAD_INFO_IS_DAEMON;
103+
@Nullable private static final Method GET_THREADS;
78104

79105
static {
80106
MethodHandle isDaemon;
@@ -86,6 +112,17 @@ List<AutoCloseable> registerObservers(OpenTelemetry openTelemetry, ThreadMXBean
86112
isDaemon = null;
87113
}
88114
THREAD_INFO_IS_DAEMON = isDaemon;
115+
Method getThreads = null;
116+
// only on jdk8
117+
if (THREAD_INFO_IS_DAEMON == null) {
118+
try {
119+
getThreads = Thread.class.getDeclaredMethod("getThreads");
120+
getThreads.setAccessible(true);
121+
} catch (Exception e) {
122+
getThreads = null;
123+
}
124+
}
125+
GET_THREADS = getThreads;
89126
}
90127

91128
private static boolean isJava9OrNewer() {
@@ -104,6 +141,26 @@ private static Consumer<ObservableLongMeasurement> java8Callback(ThreadMXBean th
104141
};
105142
}
106143

144+
private static Consumer<ObservableLongMeasurement> java8ThreadCallback(
145+
Supplier<Thread[]> supplier) {
146+
return measurement -> {
147+
Map<Attributes, Long> counts = new HashMap<>();
148+
for (Thread thread : supplier.get()) {
149+
Attributes threadAttributes = threadAttributes(thread);
150+
counts.compute(threadAttributes, (k, value) -> value == null ? 1 : value + 1);
151+
}
152+
counts.forEach((threadAttributes, count) -> measurement.record(count, threadAttributes));
153+
};
154+
}
155+
156+
private static Thread[] getThreads() {
157+
try {
158+
return requireNonNull((Thread[]) GET_THREADS.invoke(null));
159+
} catch (Exception e) {
160+
throw new IllegalStateException("Unexpected error happened during Thread#getThreads()", e);
161+
}
162+
}
163+
107164
private static Consumer<ObservableLongMeasurement> java9AndNewerCallback(
108165
ThreadMXBean threadBean) {
109166
return measurement -> {
@@ -132,5 +189,12 @@ private static Attributes threadAttributes(ThreadInfo threadInfo) {
132189
JvmAttributes.JVM_THREAD_DAEMON, isDaemon, JvmAttributes.JVM_THREAD_STATE, threadState);
133190
}
134191

192+
private static Attributes threadAttributes(Thread thread) {
193+
boolean isDaemon = thread.isDaemon();
194+
String threadState = thread.getState().name().toLowerCase(Locale.ROOT);
195+
return Attributes.of(
196+
JvmAttributes.JVM_THREAD_DAEMON, isDaemon, JvmAttributes.JVM_THREAD_STATE, threadState);
197+
}
198+
135199
private Threads() {}
136200
}

instrumentation/runtime-telemetry/runtime-telemetry-java8/library/src/test/java/io/opentelemetry/instrumentation/runtimemetrics/java8/ThreadsStableSemconvTest.java

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import java.lang.management.ThreadMXBean;
2121
import org.junit.jupiter.api.Test;
2222
import org.junit.jupiter.api.condition.EnabledForJreRange;
23-
import org.junit.jupiter.api.condition.EnabledOnJre;
2423
import org.junit.jupiter.api.condition.JRE;
2524
import org.junit.jupiter.api.extension.ExtendWith;
2625
import org.junit.jupiter.api.extension.RegisterExtension;
@@ -40,8 +39,7 @@ class ThreadsStableSemconvTest {
4039
@Mock private ThreadMXBean threadBean;
4140

4241
@Test
43-
@EnabledOnJre(JRE.JAVA_8)
44-
void registerObservers_Java8() {
42+
void registerObservers_Java8Jmx() {
4543
when(threadBean.getThreadCount()).thenReturn(7);
4644
when(threadBean.getDaemonThreadCount()).thenReturn(2);
4745

@@ -75,6 +73,46 @@ void registerObservers_Java8() {
7573
equalTo(JVM_THREAD_DAEMON, false))))));
7674
}
7775

76+
@Test
77+
@EnabledForJreRange(min = JRE.JAVA_9)
78+
void registerObservers_Java8Thread() {
79+
Thread threadInfo1 = mock(Thread.class, new ThreadInfoAnswer(false, Thread.State.RUNNABLE));
80+
Thread threadInfo2 = mock(Thread.class, new ThreadInfoAnswer(true, Thread.State.WAITING));
81+
82+
Thread[] threads = new Thread[] {threadInfo1, threadInfo2};
83+
84+
Threads.INSTANCE
85+
.registerObservers(testing.getOpenTelemetry(), () -> threads)
86+
.forEach(cleanup::deferCleanup);
87+
88+
testing.waitAndAssertMetrics(
89+
"io.opentelemetry.runtime-telemetry-java8",
90+
"jvm.thread.count",
91+
metrics ->
92+
metrics.anySatisfy(
93+
metricData ->
94+
assertThat(metricData)
95+
.hasInstrumentationScope(EXPECTED_SCOPE)
96+
.hasDescription("Number of executing platform threads.")
97+
.hasUnit("{thread}")
98+
.hasLongSumSatisfying(
99+
sum ->
100+
sum.isNotMonotonic()
101+
.hasPointsSatisfying(
102+
point ->
103+
point
104+
.hasValue(1)
105+
.hasAttributesSatisfying(
106+
equalTo(JVM_THREAD_DAEMON, false),
107+
equalTo(JVM_THREAD_STATE, "runnable")),
108+
point ->
109+
point
110+
.hasValue(1)
111+
.hasAttributesSatisfying(
112+
equalTo(JVM_THREAD_DAEMON, true),
113+
equalTo(JVM_THREAD_STATE, "waiting"))))));
114+
}
115+
78116
@Test
79117
@EnabledForJreRange(min = JRE.JAVA_9)
80118
void registerObservers_Java9AndNewer() {
@@ -135,7 +173,7 @@ public Object answer(InvocationOnMock invocation) {
135173
String methodName = invocation.getMethod().getName();
136174
if (methodName.equals("isDaemon")) {
137175
return isDaemon;
138-
} else if (methodName.equals("getThreadState")) {
176+
} else if (methodName.equals("getThreadState") || methodName.equals("getState")) {
139177
return state;
140178
}
141179
return null;

0 commit comments

Comments
 (0)