Skip to content

Commit

Permalink
Add gauges for jvm.threads.deadlocked and jvm.threads.deadlocked.monitor
Browse files Browse the repository at this point in the history
  • Loading branch information
rkurniawati committed Jul 12, 2024
1 parent f4be539 commit 41b28f2
Show file tree
Hide file tree
Showing 2 changed files with 187 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright 2024 VMware, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micrometer.core.instrument.binder.jvm;

import io.micrometer.common.lang.NonNullApi;
import io.micrometer.common.lang.NonNullFields;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.binder.BaseUnits;
import io.micrometer.core.instrument.binder.MeterBinder;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;

import static java.util.Collections.emptyList;

/**
* {@link MeterBinder} for JVM deadlocked threads. These metrics are somewhat expensive to
* collect, so they should not be enabled unless necessary. To enable these metrics, add
* the following to your application's code ({@code registry} is the meter registry being
* used by your application):
* <pre>{@code new JvmDeadlockedThreadMetrics().bindTo(registry);}</pre>
*
* @author Ruth Kurniawati
*/
@NonNullApi
@NonNullFields
public class JvmThreadDeadlockMetrics implements MeterBinder {

private final Iterable<Tag> tags;

public JvmThreadDeadlockMetrics() {
this(emptyList());
}

public JvmThreadDeadlockMetrics(Iterable<Tag> tags) {
this.tags = tags;
}

@Override
public void bindTo(MeterRegistry registry) {
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();

Gauge.builder("jvm.threads.deadlocked", threadBean, JvmThreadDeadlockMetrics::getDeadlockedThreadCount)
.tags(tags)
.description("The current number of threads that are deadlocked")
.baseUnit(BaseUnits.THREADS)
.register(registry);

Gauge
.builder("jvm.threads.deadlocked.monitor", threadBean,
JvmThreadDeadlockMetrics::getDeadlockedMonitorThreadCount)
.tags(tags)
.description("The current number of threads that are deadlocked on object monitors")
.baseUnit(BaseUnits.THREADS)
.register(registry);
}

// VisibleForTesting
static long getDeadlockedThreadCount(ThreadMXBean threadBean) {
final long[] deadlockedThreads = threadBean.findDeadlockedThreads();
return deadlockedThreads == null ? 0 : deadlockedThreads.length;
}

static long getDeadlockedMonitorThreadCount(ThreadMXBean threadMXBean) {
final long[] monitorDeadlockedThreads = threadMXBean.findMonitorDeadlockedThreads();
return monitorDeadlockedThreads == null ? 0 : monitorDeadlockedThreads.length;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright 2024 VMware, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micrometer.core.instrument.binder.jvm;

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.Test;

import java.lang.management.ThreadMXBean;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

/**
* Tests for {@link JvmThreadDeadlockMetrics}.
*
* @author Ruth Kurniawati
*/
class JvmThreadDeadlockMetricsTest {

private static class DeadlockedThread {

private final Thread thread;

DeadlockedThread(CountDownLatch lock1IsLocked, Lock lock1, CountDownLatch lock2IsLocked, Lock lock2) {
this.thread = new Thread(() -> {
try {
lock1.lock();
lock1IsLocked.countDown();
lock2IsLocked.await();
lock2.lockInterruptibly();
}
catch (InterruptedException ignored) {
}
});
}

void start() {
thread.start();
}

void interrupt() {
thread.interrupt();
}

}

@Test
void deadlockedThreadMetrics() {
MeterRegistry registry = new SimpleMeterRegistry();
new JvmThreadDeadlockMetrics().bindTo(registry);
final CountDownLatch lock1IsLocked = new CountDownLatch(1);
final CountDownLatch lock2IsLocked = new CountDownLatch(1);
final Lock lock1 = new ReentrantLock();
final Lock lock2 = new ReentrantLock();

final DeadlockedThread deadlockedThread1 = new DeadlockedThread(lock1IsLocked, lock1, lock2IsLocked, lock2);
final DeadlockedThread deadlockedThread2 = new DeadlockedThread(lock2IsLocked, lock2, lock1IsLocked, lock1);
deadlockedThread1.start();
deadlockedThread2.start();

Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(registry.get("jvm.threads.deadlocked").gauge().value()).isEqualTo(2);
assertThat(registry.get("jvm.threads.deadlocked.monitor").gauge().value()).isEqualTo(0);
});
deadlockedThread1.interrupt();
deadlockedThread2.interrupt();
}

@Test
void getDeadlockedThreadCountWhenFindDeadlockedThreadsIsNullShouldWork() {
ThreadMXBean threadBean = mock(ThreadMXBean.class);
when(threadBean.findDeadlockedThreads()).thenReturn(null);
assertThat(JvmThreadDeadlockMetrics.getDeadlockedThreadCount(threadBean)).isEqualTo(0);
}

@Test
void getDeadlockedThreadCountWhenFindMonitorDeadlockedThreadsIsNullShouldWork() {
ThreadMXBean threadBean = mock(ThreadMXBean.class);
when(threadBean.findMonitorDeadlockedThreads()).thenReturn(null);
assertThat(JvmThreadDeadlockMetrics.getDeadlockedMonitorThreadCount(threadBean)).isEqualTo(0);
}

}

0 comments on commit 41b28f2

Please sign in to comment.