You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by ds...@apache.org on 2022/06/06 23:58:15 UTC

[geode] branch support/1.15 updated: GEODE-8977: change ThreadMonitor to reduce how long it does a "stop the world" ThreadDump vm op (#7751)

This is an automated email from the ASF dual-hosted git repository.

dschneider pushed a commit to branch support/1.15
in repository https://gitbox.apache.org/repos/asf/geode.git


The following commit(s) were added to refs/heads/support/1.15 by this push:
     new f05ef0d363 GEODE-8977: change ThreadMonitor to reduce how long it does a "stop the world" ThreadDump vm op (#7751)
f05ef0d363 is described below

commit f05ef0d36322ac229bdfe790a89bc2e1b439645b
Author: Darrel Schneider <da...@vmware.com>
AuthorDate: Mon Jun 6 16:45:33 2022 -0700

    GEODE-8977: change ThreadMonitor to reduce how long it does a "stop the world" ThreadDump vm op (#7751)
    
    Now uses a cheaper getThreadInfo that does not get lock info by default and calls getThreadInfo for each stuck thread. These are the defaults because they have the shortest time do the the VM ThreadDump operation.
    To get locks set the system property "gemfire.threadmonitor.showLocks" to "true".
    To get ThreadInfo on all stuck threads with a single call set the system property "gemfire.threadmonitor.batchCalls" to "true".
    
    (cherry picked from commit 3df1e76ddbf2ab8f95e4b337b99b65117054af76)
---
 .../monitoring/ThreadsMonitoringProcess.java       |  78 +++++++++--
 ...nitTest.java => ThreadsMonitoringImplTest.java} |  86 +++++++++---
 .../ThreadsMonitoringProcessJUnitTest.java         |  87 ------------
 .../monitoring/ThreadsMonitoringProcessTest.java   | 153 +++++++++++++++++++++
 .../executor/AbstractExecutorGroupJUnitTest.java   |   2 +-
 5 files changed, 290 insertions(+), 116 deletions(-)

diff --git a/geode-core/src/main/java/org/apache/geode/internal/monitoring/ThreadsMonitoringProcess.java b/geode-core/src/main/java/org/apache/geode/internal/monitoring/ThreadsMonitoringProcess.java
index b2802fb95a..04bc95d9c7 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/monitoring/ThreadsMonitoringProcess.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/monitoring/ThreadsMonitoringProcess.java
@@ -17,6 +17,7 @@ package org.apache.geode.internal.monitoring;
 
 import java.lang.management.ManagementFactory;
 import java.lang.management.ThreadInfo;
+import java.lang.management.ThreadMXBean;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -119,30 +120,87 @@ public class ThreadsMonitoringProcess extends TimerTask {
     return result;
   }
 
-  @VisibleForTesting
-  public static Map<Long, ThreadInfo> createThreadInfoMap(Set<Long> stuckThreadIds) {
-    /*
-     * NOTE: at least some implementations of getThreadInfo(long[], boolean, boolean)
-     * will core dump if the long array contains a duplicate value.
-     * That is why stuckThreadIds is a Set instead of a List.
-     */
+  /**
+   * If set to true, then the JVM will be asked for what locks a thread holds.
+   * This is extra expensive to ask for on some JVMs so be careful setting this to true.
+   */
+  private static final boolean SHOW_LOCKS = Boolean.getBoolean("gemfire.threadmonitor.showLocks");
+  /**
+   * If set to true, then the JVM will be asked for all potential stuck threads with one call.
+   * Since getThreadInfo on many JVMs, stops ALL threads from running, and since getting info
+   * on multiple threads with one call is additional work, setting this can cause an extra long
+   * stop the world that can then cause other problems (like a forced disconnect).
+   * So be careful setting this to true.
+   */
+  private static final boolean BATCH_CALLS = Boolean.getBoolean("gemfire.threadmonitor.batchCalls");
+
+  private static Map<Long, ThreadInfo> createThreadInfoMap(Set<Long> stuckThreadIds) {
+    return createThreadInfoMap(stuckThreadIds, SHOW_LOCKS, BATCH_CALLS);
+  }
+
+  public static Map<Long, ThreadInfo> createThreadInfoMap(Set<Long> stuckThreadIds,
+      boolean showLocks, boolean batchCalls) {
+    return createThreadInfoMap(ManagementFactory.getThreadMXBean(), stuckThreadIds, showLocks,
+        batchCalls);
+  }
+
+  static Map<Long, ThreadInfo> createThreadInfoMap(ThreadMXBean threadMXBean,
+      Set<Long> stuckThreadIds,
+      final boolean showLocks, final boolean batchCalls) {
     if (stuckThreadIds.isEmpty()) {
       return Collections.emptyMap();
     }
+    logger.info(
+        "Obtaining ThreadInfo for {} threads. Configuration: showLocks={} batchCalls={}. This is an expensive operation for the JVM and on most JVMs causes all threads to be paused.",
+        stuckThreadIds.size(), showLocks, batchCalls);
+    Map<Long, ThreadInfo> result = new HashMap<>();
+    if (batchCalls) {
+      createThreadInfoMapUsingSingleCall(threadMXBean, stuckThreadIds, showLocks, result);
+    } else {
+      for (long id : stuckThreadIds) {
+        ThreadInfo threadInfo = createThreadInfoForSingleThread(threadMXBean, showLocks, id);
+        if (threadInfo != null) {
+          result.put(threadInfo.getThreadId(), threadInfo);
+        }
+      }
+    }
+    logger.info("finished obtaining ThreadInfo");
+    return result;
+  }
+
+  private static ThreadInfo createThreadInfoForSingleThread(
+      ThreadMXBean threadMXBean, boolean showLocks, long id) {
+    ThreadInfo threadInfo;
+    if (showLocks) {
+      ThreadInfo[] threadInfos =
+          threadMXBean.getThreadInfo(new long[] {id}, true, true);
+      threadInfo = threadInfos[0];
+    } else {
+      threadInfo = threadMXBean.getThreadInfo(id, Integer.MAX_VALUE);
+    }
+    return threadInfo;
+  }
+
+  private static void createThreadInfoMapUsingSingleCall(
+      ThreadMXBean threadMXBean, Set<Long> stuckThreadIds, boolean showLocks,
+      Map<Long, ThreadInfo> result) {
     long[] ids = new long[stuckThreadIds.size()];
     int idx = 0;
     for (long id : stuckThreadIds) {
       ids[idx] = id;
       idx++;
     }
-    ThreadInfo[] threadInfos = ManagementFactory.getThreadMXBean().getThreadInfo(ids, true, true);
-    Map<Long, ThreadInfo> result = new HashMap<>();
+    /*
+     * NOTE: at least some implementations of getThreadInfo(long[], boolean, boolean)
+     * will core dump if the long array contains a duplicate value.
+     * That is why stuckThreadIds is a Set instead of a List.
+     */
+    ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(ids, showLocks, showLocks);
     for (ThreadInfo threadInfo : threadInfos) {
       if (threadInfo != null) {
         result.put(threadInfo.getThreadId(), threadInfo);
       }
     }
-    return result;
   }
 
   private void addLockOwnerThreadId(Set<Long> stuckThreadIds, long threadId) {
diff --git a/geode-core/src/test/java/org/apache/geode/internal/monitoring/ThreadsMonitoringImplJUnitTest.java b/geode-core/src/test/java/org/apache/geode/internal/monitoring/ThreadsMonitoringImplTest.java
similarity index 64%
rename from geode-core/src/test/java/org/apache/geode/internal/monitoring/ThreadsMonitoringImplJUnitTest.java
rename to geode-core/src/test/java/org/apache/geode/internal/monitoring/ThreadsMonitoringImplTest.java
index 78af92ab7f..eb2da19155 100644
--- a/geode-core/src/test/java/org/apache/geode/internal/monitoring/ThreadsMonitoringImplJUnitTest.java
+++ b/geode-core/src/test/java/org/apache/geode/internal/monitoring/ThreadsMonitoringImplTest.java
@@ -15,37 +15,35 @@
 package org.apache.geode.internal.monitoring;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
 
 import org.apache.geode.internal.monitoring.ThreadsMonitoring.Mode;
 import org.apache.geode.internal.monitoring.executor.AbstractExecutor;
 import org.apache.geode.internal.monitoring.executor.FunctionExecutionPooledExecutorGroup;
+import org.apache.geode.internal.monitoring.executor.PooledExecutorGroup;
 
 /**
  * Contains simple tests for the {@link org.apache.geode.internal.monitoring.ThreadsMonitoringImpl}.
  *
  * @since Geode 1.5
  */
-public class ThreadsMonitoringImplJUnitTest {
+public class ThreadsMonitoringImplTest {
+  private static final int TIME_LIMIT_MILLIS = 1000;
 
   private ThreadsMonitoringImpl threadsMonitoringImpl;
 
-  @Before
-  public void before() {
-    threadsMonitoringImpl = new ThreadsMonitoringImpl(null, 100000, 0, false);
-  }
-
-  @After
+  @AfterEach
   public void after() {
     threadsMonitoringImpl.close();
   }
@@ -55,6 +53,7 @@ public class ThreadsMonitoringImplJUnitTest {
    */
   @Test
   public void testStartMonitor() {
+    threadsMonitoringImpl = new ThreadsMonitoringImpl(null, 100000, 0, false);
     assertTrue(threadsMonitoringImpl.startMonitor(Mode.FunctionExecutor));
     assertTrue(threadsMonitoringImpl.startMonitor(Mode.PooledExecutor));
     assertTrue(threadsMonitoringImpl.startMonitor(Mode.SerialQueuedExecutor));
@@ -67,6 +66,7 @@ public class ThreadsMonitoringImplJUnitTest {
 
   @Test
   public void verifyMonitorLifeCycle() {
+    threadsMonitoringImpl = new ThreadsMonitoringImpl(null, 100000, 0, false);
     assertFalse(threadsMonitoringImpl.isMonitoring());
     threadsMonitoringImpl.startMonitor(Mode.FunctionExecutor);
     assertTrue(threadsMonitoringImpl.isMonitoring());
@@ -76,6 +76,7 @@ public class ThreadsMonitoringImplJUnitTest {
 
   @Test
   public void verifyExecutorMonitoringLifeCycle() {
+    threadsMonitoringImpl = new ThreadsMonitoringImpl(null, 100000, 0, false);
     AbstractExecutor executor =
         threadsMonitoringImpl.createAbstractExecutor(Mode.P2PReaderExecutor);
     assertThat(threadsMonitoringImpl.isMonitoring(executor)).isFalse();
@@ -95,24 +96,28 @@ public class ThreadsMonitoringImplJUnitTest {
 
   @Test
   public void createAbstractExecutorIsAssociatedWithCallingThread() {
+    threadsMonitoringImpl = new ThreadsMonitoringImpl(null, 100000, 0, false);
     AbstractExecutor executor = threadsMonitoringImpl.createAbstractExecutor(Mode.FunctionExecutor);
     assertThat(executor.getThreadID()).isEqualTo(Thread.currentThread().getId());
   }
 
   @Test
   public void createAbstractExecutorDoesNotSetStartTime() {
+    threadsMonitoringImpl = new ThreadsMonitoringImpl(null, 100000, 0, false);
     AbstractExecutor executor = threadsMonitoringImpl.createAbstractExecutor(Mode.FunctionExecutor);
     assertThat(executor.getStartTime()).isEqualTo(0);
   }
 
   @Test
   public void createAbstractExecutorSetsNumIterationsStuckToZero() {
+    threadsMonitoringImpl = new ThreadsMonitoringImpl(null, 100000, 0, false);
     AbstractExecutor executor = threadsMonitoringImpl.createAbstractExecutor(Mode.FunctionExecutor);
     assertThat(executor.getNumIterationsStuck()).isEqualTo((short) 0);
   }
 
   @Test
   public void createAbstractExecutorSetsExpectedGroupName() {
+    threadsMonitoringImpl = new ThreadsMonitoringImpl(null, 100000, 0, false);
     AbstractExecutor executor = threadsMonitoringImpl.createAbstractExecutor(Mode.FunctionExecutor);
     assertThat(executor.getGroupName()).isEqualTo(FunctionExecutionPooledExecutorGroup.GROUPNAME);
   }
@@ -122,16 +127,17 @@ public class ThreadsMonitoringImplJUnitTest {
    */
   @Test
   public void testClosure() {
-    ThreadsMonitoringImpl liveMonitor = new ThreadsMonitoringImpl(null, 100000, 0, true);
-    assertTrue(liveMonitor.getThreadsMonitoringProcess() != null);
-    assertFalse(liveMonitor.isClosed());
-    liveMonitor.close();
-    assertTrue(liveMonitor.isClosed());
-    assertFalse(liveMonitor.getThreadsMonitoringProcess() != null);
+    threadsMonitoringImpl = new ThreadsMonitoringImpl(null, 100000, 0, true);
+    assertNotNull(threadsMonitoringImpl.getThreadsMonitoringProcess());
+    assertFalse(threadsMonitoringImpl.isClosed());
+    threadsMonitoringImpl.close();
+    assertTrue(threadsMonitoringImpl.isClosed());
+    assertNull(threadsMonitoringImpl.getThreadsMonitoringProcess());
   }
 
   @Test
   public void updateThreadStatusCallsSetStartTime() {
+    threadsMonitoringImpl = new ThreadsMonitoringImpl(null, 100000, 0, false);
     AbstractExecutor executor = mock(AbstractExecutor.class);
     long threadId = Thread.currentThread().getId();
 
@@ -142,8 +148,52 @@ public class ThreadsMonitoringImplJUnitTest {
 
   @Test
   public void updateThreadStatusWithoutExecutorInMapDoesNotCallSetStartTime() {
+    threadsMonitoringImpl = new ThreadsMonitoringImpl(null, 100000, 0, false);
     AbstractExecutor executor = mock(AbstractExecutor.class);
     threadsMonitoringImpl.updateThreadStatus();
     verify(executor, never()).setStartTime(any(Long.class));
   }
+
+  /**
+   * Tests that indeed thread is considered stuck when it should
+   */
+  @Test
+  public void testThreadIsStuck() {
+    threadsMonitoringImpl = new ThreadsMonitoringImpl(null, 100000, TIME_LIMIT_MILLIS);
+
+    final long threadID = 123456;
+
+    AbstractExecutor absExtgroup = new PooledExecutorGroup();
+    absExtgroup.setStartTime(absExtgroup.getStartTime() - TIME_LIMIT_MILLIS - 1);
+
+    threadsMonitoringImpl.getMonitorMap().put(threadID, absExtgroup);
+
+    assertTrue(threadsMonitoringImpl.getThreadsMonitoringProcess().mapValidation());
+  }
+
+  @Test
+  public void monitorHandlesDefunctThread() {
+    threadsMonitoringImpl = new ThreadsMonitoringImpl(null, 100000, TIME_LIMIT_MILLIS);
+    final long threadID = Long.MAX_VALUE;
+
+    AbstractExecutor absExtgroup = new PooledExecutorGroup(threadID);
+    absExtgroup.setStartTime(absExtgroup.getStartTime() - TIME_LIMIT_MILLIS - 1);
+
+    threadsMonitoringImpl.getMonitorMap().put(threadID, absExtgroup);
+
+    assertTrue(threadsMonitoringImpl.getThreadsMonitoringProcess().mapValidation());
+  }
+
+  /**
+   * Tests that indeed thread is NOT considered stuck when it shouldn't
+   */
+  @Test
+  public void testThreadIsNotStuck() {
+    threadsMonitoringImpl = new ThreadsMonitoringImpl(null, 100000, TIME_LIMIT_MILLIS);
+
+    threadsMonitoringImpl.startMonitor(Mode.PooledExecutor);
+
+    assertFalse(threadsMonitoringImpl.getThreadsMonitoringProcess().mapValidation());
+  }
+
 }
diff --git a/geode-core/src/test/java/org/apache/geode/internal/monitoring/ThreadsMonitoringProcessJUnitTest.java b/geode-core/src/test/java/org/apache/geode/internal/monitoring/ThreadsMonitoringProcessJUnitTest.java
deleted file mode 100644
index b3c21170c1..0000000000
--- a/geode-core/src/test/java/org/apache/geode/internal/monitoring/ThreadsMonitoringProcessJUnitTest.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
- * agreements. See the NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The ASF licenses this file to You 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
- *
- * http://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 org.apache.geode.internal.monitoring;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import org.apache.geode.internal.monitoring.ThreadsMonitoring.Mode;
-import org.apache.geode.internal.monitoring.executor.AbstractExecutor;
-import org.apache.geode.internal.monitoring.executor.PooledExecutorGroup;
-
-/**
- * Contains simple tests for the {@link org.apache.geode.internal.monitoring.ThreadsMonitoringImpl}.
- *
- * @since Geode 1.5
- */
-public class ThreadsMonitoringProcessJUnitTest {
-
-  private static final int TIME_LIMIT_MILLIS = 1000;
-
-  private ThreadsMonitoringImpl threadsMonitoringImpl;
-
-  @Before
-  public void before() {
-    threadsMonitoringImpl = new ThreadsMonitoringImpl(null, 100000, TIME_LIMIT_MILLIS);
-  }
-
-  /**
-   * Tests that indeed thread is considered stuck when it should
-   */
-  @Test
-  public void testThreadIsStuck() {
-
-    final long threadID = 123456;
-
-    AbstractExecutor absExtgroup = new PooledExecutorGroup();
-    absExtgroup.setStartTime(absExtgroup.getStartTime() - TIME_LIMIT_MILLIS - 1);
-
-    threadsMonitoringImpl.getMonitorMap().put(threadID, absExtgroup);
-
-    assertTrue(threadsMonitoringImpl.getThreadsMonitoringProcess().mapValidation());
-
-    threadsMonitoringImpl.close();
-  }
-
-  @Test
-  public void monitorHandlesDefunctThread() {
-    final long threadID = Long.MAX_VALUE;
-
-    AbstractExecutor absExtgroup = new PooledExecutorGroup(threadID);
-    absExtgroup.setStartTime(absExtgroup.getStartTime() - TIME_LIMIT_MILLIS - 1);
-
-    threadsMonitoringImpl.getMonitorMap().put(threadID, absExtgroup);
-
-    assertTrue(threadsMonitoringImpl.getThreadsMonitoringProcess().mapValidation());
-
-    threadsMonitoringImpl.close();
-  }
-
-  /**
-   * Tests that indeed thread is NOT considered stuck when it shouldn't
-   */
-  @Test
-  public void testThreadIsNotStuck() {
-
-    threadsMonitoringImpl.startMonitor(Mode.PooledExecutor);
-
-    assertFalse(threadsMonitoringImpl.getThreadsMonitoringProcess().mapValidation());
-
-    threadsMonitoringImpl.close();
-  }
-}
diff --git a/geode-core/src/test/java/org/apache/geode/internal/monitoring/ThreadsMonitoringProcessTest.java b/geode-core/src/test/java/org/apache/geode/internal/monitoring/ThreadsMonitoringProcessTest.java
new file mode 100644
index 0000000000..74179c84e5
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/monitoring/ThreadsMonitoringProcessTest.java
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.geode.internal.monitoring;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.AdditionalMatchers.aryEq;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.lang.management.ThreadInfo;
+import java.lang.management.ThreadMXBean;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.Test;
+
+public class ThreadsMonitoringProcessTest {
+  ThreadMXBean threadMXBean = mock(ThreadMXBean.class);
+
+  @Test
+  public void createThreadInfoMapWithNoIdsReturnsEmptyMap() {
+    Map<Long, ThreadInfo> map =
+        ThreadsMonitoringProcess.createThreadInfoMap(threadMXBean, Collections.emptySet(), false,
+            false);
+    assertThat(map).isEmpty();
+  }
+
+  @Test
+  public void createThreadInfoMapWithIdsNoLocksNoBatchReturnsExpectedResult() {
+    Set<Long> threadIdSet = createThreadIdSet();
+    ThreadInfo t1Info = createThreadInfoForNoLocksNoBatch(1L);
+    ThreadInfo t2Info = createThreadInfoForNoLocksNoBatch(2L);
+    ThreadInfo t3Info = createThreadInfoForNoLocksNoBatch(3L);
+    Map<Long, ThreadInfo> expectedResult = createExpectedResult(t1Info, t2Info, t3Info);
+
+    Map<Long, ThreadInfo> map =
+        ThreadsMonitoringProcess.createThreadInfoMap(threadMXBean, threadIdSet, false,
+            false);
+
+    assertThat(map).isEqualTo(expectedResult);
+  }
+
+  @NotNull
+  private Map<Long, ThreadInfo> createExpectedResult(ThreadInfo t1Info, ThreadInfo t2Info,
+      ThreadInfo t3Info) {
+    Map<Long, ThreadInfo> expectedResult = new HashMap<>();
+    expectedResult.put(1L, t1Info);
+    expectedResult.put(2L, t2Info);
+    expectedResult.put(3L, t3Info);
+    return expectedResult;
+  }
+
+  @NotNull
+  private Set<Long> createThreadIdSet() {
+    Set<Long> threadIds = new HashSet<>();
+    threadIds.add(1L);
+    threadIds.add(2L);
+    threadIds.add(3L);
+    return threadIds;
+  }
+
+  @NotNull
+  private ThreadInfo createThreadInfoForNoLocksNoBatch(long id) {
+    ThreadInfo result = mock(ThreadInfo.class);
+    when(result.getThreadId()).thenReturn(id);
+    when(threadMXBean.getThreadInfo(eq(id), eq(Integer.MAX_VALUE))).thenReturn(result);
+    return result;
+  }
+
+  @Test
+  public void createThreadInfoMapWithIdsLocksNoBatchReturnsExpectedResult() {
+    Set<Long> threadIdSet = createThreadIdSet();
+    ThreadInfo t1Info = createThreadInfoForLocksNoBatch(1L);
+    ThreadInfo t2Info = createThreadInfoForLocksNoBatch(2L);
+    ThreadInfo t3Info = createThreadInfoForLocksNoBatch(3L);
+    Map<Long, ThreadInfo> expectedResult = createExpectedResult(t1Info, t2Info, t3Info);
+
+    Map<Long, ThreadInfo> map =
+        ThreadsMonitoringProcess.createThreadInfoMap(threadMXBean, threadIdSet, true,
+            false);
+
+    assertThat(map).isEqualTo(expectedResult);
+  }
+
+  @NotNull
+  private ThreadInfo createThreadInfoForLocksNoBatch(long id) {
+    ThreadInfo result = mock(ThreadInfo.class);
+    when(result.getThreadId()).thenReturn(id);
+    when(threadMXBean.getThreadInfo(aryEq(new long[] {id}), eq(true), eq(true))).thenReturn(
+        new ThreadInfo[] {result});
+    return result;
+  }
+
+  @Test
+  public void createThreadInfoMapWithIdsLocksBatchReturnsExpectedResult() {
+    Set<Long> threadIdSet = createThreadIdSet();
+    Map<Long, ThreadInfo> expectedResult = createThreadInfoMapForBatch(threadIdSet, true);
+
+    Map<Long, ThreadInfo> map =
+        ThreadsMonitoringProcess.createThreadInfoMap(threadMXBean, threadIdSet, true,
+            true);
+
+    assertThat(map).isEqualTo(expectedResult);
+  }
+
+  @Test
+  public void createThreadInfoMapWithIdsNoLocksBatchReturnsExpectedResult() {
+    Set<Long> threadIdSet = createThreadIdSet();
+    Map<Long, ThreadInfo> expectedResult = createThreadInfoMapForBatch(threadIdSet, false);
+
+    Map<Long, ThreadInfo> map =
+        ThreadsMonitoringProcess.createThreadInfoMap(threadMXBean, threadIdSet, false,
+            true);
+
+    assertThat(map).isEqualTo(expectedResult);
+  }
+
+  @NotNull
+  private Map<Long, ThreadInfo> createThreadInfoMapForBatch(Set<Long> threadIdSet, boolean locks) {
+    long[] threadIdArray = new long[threadIdSet.size()];
+    ThreadInfo[] threadInfoArray = new ThreadInfo[threadIdSet.size()];
+    Map<Long, ThreadInfo> result = new HashMap<>();
+    int idx = 0;
+    for (Long id : threadIdSet) {
+      threadIdArray[idx] = id;
+      ThreadInfo threadInfo = mock(ThreadInfo.class);
+      when(threadInfo.getThreadId()).thenReturn(id);
+      threadInfoArray[idx] = threadInfo;
+      result.put(id, threadInfo);
+      idx++;
+    }
+    when(threadMXBean.getThreadInfo(aryEq(threadIdArray), eq(locks), eq(locks)))
+        .thenReturn(threadInfoArray);
+    return result;
+  }
+}
diff --git a/geode-core/src/test/java/org/apache/geode/internal/monitoring/executor/AbstractExecutorGroupJUnitTest.java b/geode-core/src/test/java/org/apache/geode/internal/monitoring/executor/AbstractExecutorGroupJUnitTest.java
index 2cbe3052c2..5e1270a638 100644
--- a/geode-core/src/test/java/org/apache/geode/internal/monitoring/executor/AbstractExecutorGroupJUnitTest.java
+++ b/geode-core/src/test/java/org/apache/geode/internal/monitoring/executor/AbstractExecutorGroupJUnitTest.java
@@ -104,7 +104,7 @@ public class AbstractExecutorGroupJUnitTest {
         threadIds.add(blockedThread.getId());
         threadIds.add(blockingThread.getId());
         String threadReport = executor.createThreadReport(60000,
-            ThreadsMonitoringProcess.createThreadInfoMap(threadIds));
+            ThreadsMonitoringProcess.createThreadInfoMap(threadIds, true, true));
         assertThat(threadReport)
             .contains(AbstractExecutor.LOCK_OWNER_THREAD_STACK + " for \"blocking thread\"");
         assertThat(threadReport).contains("Waiting on <" + syncObject + ">");