Skip to content

Commit 063c757

Browse files
committed
Add gauges for jvm.threads.deadlocked and jvm.threads.deadlocked.monitor
1 parent f890ca4 commit 063c757

File tree

2 files changed

+216
-0
lines changed

2 files changed

+216
-0
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright 2024 VMware, 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+
* https://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+
package io.micrometer.core.instrument.binder.jvm;
17+
18+
import io.micrometer.common.lang.NonNullApi;
19+
import io.micrometer.common.lang.NonNullFields;
20+
import io.micrometer.common.util.internal.logging.InternalLogger;
21+
import io.micrometer.common.util.internal.logging.InternalLoggerFactory;
22+
import io.micrometer.core.instrument.Gauge;
23+
import io.micrometer.core.instrument.MeterRegistry;
24+
import io.micrometer.core.instrument.Tag;
25+
import io.micrometer.core.instrument.binder.BaseUnits;
26+
import io.micrometer.core.instrument.binder.MeterBinder;
27+
28+
import java.lang.management.ManagementFactory;
29+
import java.lang.management.ThreadMXBean;
30+
31+
import static java.util.Collections.emptyList;
32+
33+
/**
34+
* {@link MeterBinder} for JVM deadlocked threads. These metrics are somewhat expensive to
35+
* collect, so they should not be enabled unless necessary. To enable these metrics, add
36+
* the following to your application's code ({@code registry} is the meter registry being
37+
* used by your application):
38+
* <pre>{@code new JvmDeadlockedThreadMetrics().bindTo(registry);}</pre>
39+
*
40+
* @author Ruth Kurniawati
41+
*/
42+
@NonNullApi
43+
@NonNullFields
44+
public class JvmThreadDeadlockMetrics implements MeterBinder {
45+
46+
private static final InternalLogger log = InternalLoggerFactory.getInstance(JvmThreadDeadlockMetrics.class);
47+
48+
private final Iterable<Tag> tags;
49+
50+
public JvmThreadDeadlockMetrics() {
51+
this(emptyList());
52+
}
53+
54+
public JvmThreadDeadlockMetrics(Iterable<Tag> tags) {
55+
this.tags = tags;
56+
}
57+
58+
@Override
59+
public void bindTo(MeterRegistry registry) {
60+
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
61+
62+
if (threadBean.isObjectMonitorUsageSupported()) {
63+
Gauge.builder("jvm.threads.deadlocked", threadBean, JvmThreadDeadlockMetrics::getDeadlockedThreadCount)
64+
.tags(tags)
65+
.description("The current number of threads that are deadlocked")
66+
.baseUnit(BaseUnits.THREADS)
67+
.register(registry);
68+
}
69+
else {
70+
log.warn("jvm.threads.deadlocked is not available on this JVM");
71+
}
72+
73+
Gauge
74+
.builder("jvm.threads.deadlocked.monitor", threadBean,
75+
JvmThreadDeadlockMetrics::getDeadlockedMonitorThreadCount)
76+
.tags(tags)
77+
.description("The current number of threads that are deadlocked on object monitors")
78+
.baseUnit(BaseUnits.THREADS)
79+
.register(registry);
80+
}
81+
82+
// VisibleForTesting
83+
static long getDeadlockedThreadCount(ThreadMXBean threadBean) {
84+
final long[] deadlockedThreads = threadBean.findDeadlockedThreads();
85+
return deadlockedThreads == null ? 0 : deadlockedThreads.length;
86+
}
87+
88+
static long getDeadlockedMonitorThreadCount(ThreadMXBean threadBean) {
89+
final long[] monitorDeadlockedThreads = threadBean.findMonitorDeadlockedThreads();
90+
return monitorDeadlockedThreads == null ? 0 : monitorDeadlockedThreads.length;
91+
}
92+
93+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Copyright 2024 VMware, 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+
* https://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+
package io.micrometer.core.instrument.binder.jvm;
17+
18+
import io.micrometer.core.instrument.MeterRegistry;
19+
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
20+
import org.awaitility.Awaitility;
21+
import org.junit.jupiter.api.Test;
22+
import org.mockito.MockedStatic;
23+
import org.mockito.Mockito;
24+
25+
import java.lang.management.ManagementFactory;
26+
import java.lang.management.ThreadMXBean;
27+
import java.util.concurrent.CountDownLatch;
28+
import java.util.concurrent.TimeUnit;
29+
import java.util.concurrent.locks.Lock;
30+
import java.util.concurrent.locks.ReentrantLock;
31+
32+
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
33+
import static org.mockito.Mockito.mock;
34+
import static org.mockito.Mockito.when;
35+
36+
/**
37+
* Tests for {@link JvmThreadDeadlockMetrics}.
38+
*
39+
* @author Ruth Kurniawati
40+
*/
41+
class JvmThreadDeadlockMetricsTest {
42+
43+
private static class DeadlockedThread {
44+
45+
private final Thread thread;
46+
47+
DeadlockedThread(CountDownLatch lock1IsLocked, Lock lock1, CountDownLatch lock2IsLocked, Lock lock2) {
48+
this.thread = new Thread(() -> {
49+
try {
50+
lock1.lock();
51+
lock1IsLocked.countDown();
52+
lock2IsLocked.await();
53+
lock2.lockInterruptibly();
54+
}
55+
catch (InterruptedException ignored) {
56+
}
57+
});
58+
}
59+
60+
void start() {
61+
thread.start();
62+
}
63+
64+
void interrupt() {
65+
thread.interrupt();
66+
}
67+
68+
}
69+
70+
@Test
71+
void deadlockedThreadMetrics() {
72+
MeterRegistry registry = new SimpleMeterRegistry();
73+
new JvmThreadDeadlockMetrics().bindTo(registry);
74+
final CountDownLatch lock1IsLocked = new CountDownLatch(1);
75+
final CountDownLatch lock2IsLocked = new CountDownLatch(1);
76+
final Lock lock1 = new ReentrantLock();
77+
final Lock lock2 = new ReentrantLock();
78+
79+
final DeadlockedThread deadlockedThread1 = new DeadlockedThread(lock1IsLocked, lock1, lock2IsLocked, lock2);
80+
final DeadlockedThread deadlockedThread2 = new DeadlockedThread(lock2IsLocked, lock2, lock1IsLocked, lock1);
81+
deadlockedThread1.start();
82+
deadlockedThread2.start();
83+
84+
Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> {
85+
assertThat(registry.get("jvm.threads.deadlocked").gauge().value()).isEqualTo(2);
86+
assertThat(registry.get("jvm.threads.deadlocked.monitor").gauge().value()).isEqualTo(0);
87+
});
88+
deadlockedThread1.interrupt();
89+
deadlockedThread2.interrupt();
90+
}
91+
92+
@Test
93+
void whenJvmDoesntSupportObjectMonitorUsageJvmThreadsDeadlockedMetricShouldNotBeRegistered() {
94+
MeterRegistry registry = new SimpleMeterRegistry();
95+
try (MockedStatic<ManagementFactory> mockedStatic = Mockito.mockStatic(ManagementFactory.class)) {
96+
ThreadMXBean threadBean = mock(ThreadMXBean.class);
97+
when(threadBean.isObjectMonitorUsageSupported()).thenReturn(false);
98+
mockedStatic.when(ManagementFactory::getThreadMXBean).thenReturn(threadBean);
99+
new JvmThreadDeadlockMetrics().bindTo(registry);
100+
101+
// ObjectMonitorUsage is not supported, so this metric should not be
102+
// registered
103+
assertThat(registry.find("jvm.threads.deadlocked").gauge()).isNull();
104+
// but this one is still supported
105+
assertThat(registry.find("jvm.threads.deadlocked.monitor").gauge()).isNotNull();
106+
}
107+
}
108+
109+
@Test
110+
void getDeadlockedThreadCountWhenFindDeadlockedThreadsIsNullShouldWork() {
111+
ThreadMXBean threadBean = mock(ThreadMXBean.class);
112+
when(threadBean.findDeadlockedThreads()).thenReturn(null);
113+
assertThat(JvmThreadDeadlockMetrics.getDeadlockedThreadCount(threadBean)).isEqualTo(0);
114+
}
115+
116+
@Test
117+
void getDeadlockedThreadCountWhenFindMonitorDeadlockedThreadsIsNullShouldWork() {
118+
ThreadMXBean threadBean = mock(ThreadMXBean.class);
119+
when(threadBean.findMonitorDeadlockedThreads()).thenReturn(null);
120+
assertThat(JvmThreadDeadlockMetrics.getDeadlockedMonitorThreadCount(threadBean)).isEqualTo(0);
121+
}
122+
123+
}

0 commit comments

Comments
 (0)