From 4176187e824b838639d920eee81128415955beb8 Mon Sep 17 00:00:00 2001 From: Nikola Grcevski <6207777+grcevski@users.noreply.github.com> Date: Thu, 16 Sep 2021 10:38:00 -0400 Subject: [PATCH] Use enum field for HotThreads report type (#77812) Backport of #77462. HotThreads report request type was declared as String, but used as an enum where only few values were allowed. This PR changes the type to enum allowing malformed requests to correctly report the error. This PR also introduces a small refactor to make the report getter function part of the accumulator class. --- .../action/admin/HotThreadsIT.java | 3 +- .../hotthreads/NodesHotThreadsRequest.java | 11 +- .../NodesHotThreadsRequestBuilder.java | 3 +- .../elasticsearch/monitor/jvm/HotThreads.java | 105 +++++++++++------- .../cluster/RestNodesHotThreadsAction.java | 3 +- .../NodesHotThreadsRequestTests.java | 84 ++++++++++++++ .../monitor/jvm/HotThreadsTests.java | 54 ++++++--- 7 files changed, 199 insertions(+), 64 deletions(-) create mode 100644 server/src/test/java/org/elasticsearch/action/admin/cluster/hotthreads/NodesHotThreadsRequestTests.java diff --git a/server/src/internalClusterTest/java/org/elasticsearch/action/admin/HotThreadsIT.java b/server/src/internalClusterTest/java/org/elasticsearch/action/admin/HotThreadsIT.java index 781a6af30ab2f..f89455e03f1df 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/action/admin/HotThreadsIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/action/admin/HotThreadsIT.java @@ -13,6 +13,7 @@ import org.elasticsearch.action.admin.cluster.node.hotthreads.NodesHotThreadsRequestBuilder; import org.elasticsearch.action.admin.cluster.node.hotthreads.NodesHotThreadsResponse; import org.elasticsearch.core.TimeValue; +import org.elasticsearch.monitor.jvm.HotThreads; import org.elasticsearch.test.ESIntegTestCase; import org.hamcrest.Matcher; @@ -66,7 +67,7 @@ public void testHotThreadsDontFail() throws ExecutionException, InterruptedExcep break; } assertThat(type, notNullValue()); - nodesHotThreadsRequestBuilder.setType(type); + nodesHotThreadsRequestBuilder.setType(HotThreads.ReportType.of(type)); } else { type = null; } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/node/hotthreads/NodesHotThreadsRequest.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/node/hotthreads/NodesHotThreadsRequest.java index d199d28ca4ca1..1801be97c48b7 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/node/hotthreads/NodesHotThreadsRequest.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/node/hotthreads/NodesHotThreadsRequest.java @@ -12,6 +12,7 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.core.TimeValue; +import org.elasticsearch.monitor.jvm.HotThreads; import java.io.IOException; import java.util.concurrent.TimeUnit; @@ -19,7 +20,7 @@ public class NodesHotThreadsRequest extends BaseNodesRequest { int threads = 3; - String type = "cpu"; + HotThreads.ReportType type = HotThreads.ReportType.CPU; TimeValue interval = new TimeValue(500, TimeUnit.MILLISECONDS); int snapshots = 10; boolean ignoreIdleThreads = true; @@ -29,7 +30,7 @@ public NodesHotThreadsRequest(StreamInput in) throws IOException { super(in); threads = in.readInt(); ignoreIdleThreads = in.readBoolean(); - type = in.readString(); + type = HotThreads.ReportType.of(in.readString()); interval = in.readTimeValue(); snapshots = in.readInt(); } @@ -60,12 +61,12 @@ public NodesHotThreadsRequest ignoreIdleThreads(boolean ignoreIdleThreads) { return this; } - public NodesHotThreadsRequest type(String type) { + public NodesHotThreadsRequest type(HotThreads.ReportType type) { this.type = type; return this; } - public String type() { + public HotThreads.ReportType type() { return this.type; } @@ -92,7 +93,7 @@ public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); out.writeInt(threads); out.writeBoolean(ignoreIdleThreads); - out.writeString(type); + out.writeString(type.getTypeValue()); out.writeTimeValue(interval); out.writeInt(snapshots); } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/node/hotthreads/NodesHotThreadsRequestBuilder.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/node/hotthreads/NodesHotThreadsRequestBuilder.java index d25559b0236cd..50751c7e1f9c3 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/node/hotthreads/NodesHotThreadsRequestBuilder.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/node/hotthreads/NodesHotThreadsRequestBuilder.java @@ -11,6 +11,7 @@ import org.elasticsearch.action.support.nodes.NodesOperationRequestBuilder; import org.elasticsearch.client.ElasticsearchClient; import org.elasticsearch.core.TimeValue; +import org.elasticsearch.monitor.jvm.HotThreads; public class NodesHotThreadsRequestBuilder extends NodesOperationRequestBuilder { @@ -29,7 +30,7 @@ public NodesHotThreadsRequestBuilder setIgnoreIdleThreads(boolean ignoreIdleThre return this; } - public NodesHotThreadsRequestBuilder setType(String type) { + public NodesHotThreadsRequestBuilder setType(HotThreads.ReportType type) { request.type(type); return this; } diff --git a/server/src/main/java/org/elasticsearch/monitor/jvm/HotThreads.java b/server/src/main/java/org/elasticsearch/monitor/jvm/HotThreads.java index 87957a0fa9639..e5182cb642b1e 100644 --- a/server/src/main/java/org/elasticsearch/monitor/jvm/HotThreads.java +++ b/server/src/main/java/org/elasticsearch/monitor/jvm/HotThreads.java @@ -31,15 +31,42 @@ public class HotThreads { private static final Object mutex = new Object(); + private static final StackTraceElement[] EMPTY = new StackTraceElement[0]; private static final DateFormatter DATE_TIME_FORMATTER = DateFormatter.forPattern("date_optional_time"); private int busiestThreads = 3; private TimeValue interval = new TimeValue(500, TimeUnit.MILLISECONDS); - private TimeValue threadElementsSnapshotDelay = new TimeValue(10); + private TimeValue threadElementsSnapshotDelay = new TimeValue(10, TimeUnit.MILLISECONDS); private int threadElementsSnapshotCount = 10; - private String type = "cpu"; + private ReportType type = ReportType.CPU; private boolean ignoreIdleThreads = true; + public enum ReportType { + + CPU("cpu"), + WAIT("wait"), + BLOCK("block"); + + private final String type; + + ReportType(String type) { + this.type = type; + } + + public String getTypeValue() { + return type; + } + + public static ReportType of(String type) { + for (ReportType report : values()) { + if (report.type.equals(type)) { + return report; + } + } + throw new IllegalArgumentException("type not supported [" + type + "]"); + } + } + public HotThreads interval(TimeValue interval) { this.interval = interval; return this; @@ -65,12 +92,8 @@ public HotThreads threadElementsSnapshotCount(int threadElementsSnapshotCount) { return this; } - public HotThreads type(String type) { - if ("cpu".equals(type) || "wait".equals(type) || "block".equals(type)) { - this.type = type; - } else { - throw new IllegalArgumentException("type not supported [" + type + "]"); - } + public HotThreads type(ReportType type) { + this.type = type; return this; } @@ -134,7 +157,7 @@ String innerDetect(ThreadMXBean threadBean, long currentThreadId) throws Excepti sb.append(ignoreIdleThreads); sb.append(":\n"); - Map threadInfos = new HashMap<>(); + Map threadInfos = new HashMap<>(); for (long threadId : threadBean.getAllThreadIds()) { // ignore our own thread... if (currentThreadId == threadId) { @@ -148,7 +171,7 @@ String innerDetect(ThreadMXBean threadBean, long currentThreadId) throws Excepti if (info == null) { continue; } - threadInfos.put(threadId, new MyThreadInfo(cpu, info)); + threadInfos.put(threadId, new ThreadTimeAccumulator(info, cpu)); } Thread.sleep(interval.millis()); for (long threadId : threadBean.getAllThreadIds()) { @@ -166,43 +189,25 @@ String innerDetect(ThreadMXBean threadBean, long currentThreadId) throws Excepti threadInfos.remove(threadId); continue; } - MyThreadInfo data = threadInfos.get(threadId); + ThreadTimeAccumulator data = threadInfos.get(threadId); if (data != null) { - data.setDelta(cpu, info); + data.setDelta(info, cpu); } else { threadInfos.remove(threadId); } } // sort by delta CPU time on thread. - List hotties = new ArrayList<>(threadInfos.values()); + List hotties = new ArrayList<>(threadInfos.values()); final int busiestThreads = Math.min(this.busiestThreads, hotties.size()); // skip that for now - final ToLongFunction getter; - if ("cpu".equals(type)) { - getter = o -> { - assert o.cpuTime >= -1 : "cpu time should not be negative, but was " + o.cpuTime + ", thread info: " + o.info; - return o.cpuTime; - }; - } else if ("wait".equals(type)) { - getter = o -> { - assert o.waitedTime >= -1 : "waited time should not be negative, but was " + o.waitedTime + ", thread info: " + o.info; - return o.waitedTime; - }; - } else if ("block".equals(type)) { - getter = o -> { - assert o.blockedTime >= -1 : "blocked time should not be negative, but was " + o.blockedTime + ", thread info: " + o.info; - return o.blockedTime; - }; - } else { - throw new IllegalArgumentException("expected thread type to be either 'cpu', 'wait', or 'block', but was " + type); - } + final ToLongFunction getter = ThreadTimeAccumulator.valueGetterForReportType(type); CollectionUtil.introSort(hotties, Comparator.comparingLong(getter).reversed()); // analyse N stack traces for M busiest threads long[] ids = new long[busiestThreads]; for (int i = 0; i < busiestThreads; i++) { - MyThreadInfo info = hotties.get(i); + ThreadTimeAccumulator info = hotties.get(i); ids[i] = info.info.getThreadId(); } ThreadInfo[][] allInfos = new ThreadInfo[threadElementsSnapshotCount][]; @@ -231,7 +236,7 @@ String innerDetect(ThreadMXBean threadBean, long currentThreadId) throws Excepti } double percent = (((double) time) / interval.nanos()) * 100; sb.append(String.format(Locale.ROOT, "%n%4.1f%% (%s out of %s) %s usage by thread '%s'%n", - percent, TimeValue.timeValueNanos(time), interval, type, threadName)); + percent, TimeValue.timeValueNanos(time), interval, type.getTypeValue(), threadName)); // for each snapshot (2nd array index) find later snapshot for same thread with max number of // identical StackTraceElements (starting from end of each) boolean[] done = new boolean[threadElementsSnapshotCount]; @@ -276,8 +281,6 @@ String innerDetect(ThreadMXBean threadBean, long currentThreadId) throws Excepti return sb.toString(); } - private static final StackTraceElement[] EMPTY = new StackTraceElement[0]; - int similarity(ThreadInfo threadInfo, ThreadInfo threadInfo0) { StackTraceElement[] s1 = threadInfo == null ? EMPTY : threadInfo.getStackTrace(); StackTraceElement[] s2 = threadInfo0 == null ? EMPTY : threadInfo0.getStackTrace(); @@ -293,7 +296,7 @@ int similarity(ThreadInfo threadInfo, ThreadInfo threadInfo0) { } - class MyThreadInfo { + static class ThreadTimeAccumulator { long cpuTime; long blockedCount; long blockedTime; @@ -302,7 +305,7 @@ class MyThreadInfo { boolean deltaDone; ThreadInfo info; - MyThreadInfo(long cpuTime, ThreadInfo info) { + ThreadTimeAccumulator(ThreadInfo info, long cpuTime) { blockedCount = info.getBlockedCount(); blockedTime = info.getBlockedTime(); waitedCount = info.getWaitedCount(); @@ -311,7 +314,7 @@ class MyThreadInfo { this.info = info; } - void setDelta(long cpuTime, ThreadInfo info) { + void setDelta(ThreadInfo info, long cpuTime) { if (deltaDone) throw new IllegalStateException("setDelta already called once"); blockedCount = info.getBlockedCount() - blockedCount; blockedTime = info.getBlockedTime() - blockedTime; @@ -321,5 +324,29 @@ void setDelta(long cpuTime, ThreadInfo info) { deltaDone = true; this.info = info; } + + static ToLongFunction valueGetterForReportType(ReportType type) { + switch (type) { + case CPU: + return o -> { + assert o.cpuTime >= -1 : + "cpu time should not be negative, but was " + o.cpuTime + ", thread info: " + o.info; + return o.cpuTime; + }; + case WAIT: + return o -> { + assert o.waitedTime >= -1 : + "waited time should not be negative, but was " + o.waitedTime + ", thread info: " + o.info; + return o.waitedTime; + }; + case BLOCK: + return o -> { + assert o.blockedTime >= -1 : + "blocked time should not be negative, but was " + o.blockedTime + ", thread info: " + o.info; + return o.blockedTime; + }; + } + throw new IllegalArgumentException("expected thread type to be either 'cpu', 'wait', or 'block', but was " + type); + } } } diff --git a/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestNodesHotThreadsAction.java b/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestNodesHotThreadsAction.java index 9b919ffe31439..d2f5c8cb1ed1e 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestNodesHotThreadsAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestNodesHotThreadsAction.java @@ -15,6 +15,7 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.core.RestApiVersion; import org.elasticsearch.core.TimeValue; +import org.elasticsearch.monitor.jvm.HotThreads; import org.elasticsearch.rest.BaseRestHandler; import org.elasticsearch.rest.BytesRestResponse; import org.elasticsearch.rest.RestRequest; @@ -93,7 +94,7 @@ public RestChannelConsumer prepareRequest(final RestRequest request, final NodeC NodesHotThreadsRequest nodesHotThreadsRequest = new NodesHotThreadsRequest(nodesIds); nodesHotThreadsRequest.threads(request.paramAsInt("threads", nodesHotThreadsRequest.threads())); nodesHotThreadsRequest.ignoreIdleThreads(request.paramAsBoolean("ignore_idle_threads", nodesHotThreadsRequest.ignoreIdleThreads())); - nodesHotThreadsRequest.type(request.param("type", nodesHotThreadsRequest.type())); + nodesHotThreadsRequest.type(HotThreads.ReportType.of(request.param("type", nodesHotThreadsRequest.type().getTypeValue()))); nodesHotThreadsRequest.interval(TimeValue.parseTimeValue(request.param("interval"), nodesHotThreadsRequest.interval(), "interval")); nodesHotThreadsRequest.snapshots(request.paramAsInt("snapshots", nodesHotThreadsRequest.snapshots())); nodesHotThreadsRequest.timeout(request.param("timeout")); diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/hotthreads/NodesHotThreadsRequestTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/hotthreads/NodesHotThreadsRequestTests.java new file mode 100644 index 0000000000000..f433922f3cb5d --- /dev/null +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/hotthreads/NodesHotThreadsRequestTests.java @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.action.admin.cluster.hotthreads; + +import org.elasticsearch.action.admin.cluster.node.hotthreads.NodesHotThreadsRequest; +import org.elasticsearch.action.support.nodes.BaseNodesRequest; +import org.elasticsearch.common.io.stream.BytesStreamOutput; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.core.TimeValue; +import org.elasticsearch.monitor.jvm.HotThreads; +import org.elasticsearch.test.ESTestCase; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +public class NodesHotThreadsRequestTests extends ESTestCase { + + /** Simple override of BaseNodesRequest to ensure we read the + * common fields of the nodes request. + */ + static class NodesHotThreadsRequestHelper extends BaseNodesRequest { + NodesHotThreadsRequestHelper(StreamInput in) throws IOException { + super(in); + } + + NodesHotThreadsRequestHelper(String... nodesIds) { + super(nodesIds); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + } + } + + public void testBWCSerialization() throws IOException { + BytesStreamOutput out = new BytesStreamOutput(); + + TimeValue sampleInterval = new TimeValue(50, TimeUnit.MINUTES); + + NodesHotThreadsRequestHelper outHelper = new NodesHotThreadsRequestHelper("123"); + + outHelper.writeTo(out); + // Write manually some values that differ from the defaults + // in NodesHotThreadsRequest + out.writeInt(4); // threads + out.writeBoolean(false); // ignoreIdleThreads + out.writeString("block"); // type + out.writeTimeValue(sampleInterval); // interval + out.writeInt(3); // snapshots + + NodesHotThreadsRequest inRequest = new NodesHotThreadsRequest(out.bytes().streamInput()); + + assertEquals(4, inRequest.threads()); + assertFalse(inRequest.ignoreIdleThreads()); + assertEquals(HotThreads.ReportType.BLOCK, inRequest.type()); + assertEquals(sampleInterval, inRequest.interval()); + assertEquals(3, inRequest.snapshots()); + + // Change the report type enum + inRequest.type(HotThreads.ReportType.WAIT); + + BytesStreamOutput writeOut = new BytesStreamOutput(); + inRequest.writeTo(writeOut); + + StreamInput whatWeWrote = writeOut.bytes().streamInput(); + + // We construct the helper to read the common serialized fields from the in. + new NodesHotThreadsRequestHelper(whatWeWrote); + // Make sure we serialized in the following format + assertEquals(4, whatWeWrote.readInt()); + assertFalse(whatWeWrote.readBoolean()); + assertEquals("wait", whatWeWrote.readString()); // lowercase enum value, not label + assertEquals(sampleInterval, whatWeWrote.readTimeValue()); + assertEquals(3, whatWeWrote.readInt()); + } +} diff --git a/server/src/test/java/org/elasticsearch/monitor/jvm/HotThreadsTests.java b/server/src/test/java/org/elasticsearch/monitor/jvm/HotThreadsTests.java index fe05d049c3ba4..ec4e1834b69ef 100644 --- a/server/src/test/java/org/elasticsearch/monitor/jvm/HotThreadsTests.java +++ b/server/src/test/java/org/elasticsearch/monitor/jvm/HotThreadsTests.java @@ -15,7 +15,6 @@ import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.stream.Collectors; @@ -30,12 +29,12 @@ public class HotThreadsTests extends ESTestCase { public void testSupportedThreadsReportType() { for (String type: new String[] {"unsupported", "", null, "CPU", "WAIT", "BLOCK" }) { - expectThrows(IllegalArgumentException.class, () -> new HotThreads().type(type)); + expectThrows(IllegalArgumentException.class, () -> new HotThreads().type(HotThreads.ReportType.of(type))); } for (String type : new String[] { "cpu", "wait", "block" }) { try { - new HotThreads().type(type); + new HotThreads().type(HotThreads.ReportType.of(type)); } catch (IllegalArgumentException e) { fail(String.format(Locale.ROOT, "IllegalArgumentException called when creating HotThreads for supported type [%s]", type)); } @@ -69,7 +68,7 @@ public void testIdleThreadsDetection() { } List testJvmStack = makeThreadStackHelper( - Arrays.asList( + org.elasticsearch.core.List.of( new String[]{"org.elasticsearch.monitor.test", "methodOne"}, new String[]{"org.elasticsearch.monitor.testOther", "methodTwo"}, new String[]{"org.elasticsearch.monitor.test", "methodThree"}, @@ -83,7 +82,7 @@ public void testIdleThreadsDetection() { assertFalse(HotThreads.isIdleThread(notIdleThread)); List idleThreadStackElements = makeThreadStackHelper( - Arrays.asList( + org.elasticsearch.core.List.of( new String[]{"java.util.concurrent.ThreadPoolExecutor", "getTask"}, new String[]{"sun.nio.ch.SelectorImpl", "select"}, new String[]{"org.elasticsearch.threadpool.ThreadPool$CachedTimeThread", "run"}, @@ -115,25 +114,25 @@ public void testIdleThreadsDetection() { public void testSimilarity() { StackTraceElement[] stackOne = makeThreadStackHelper( - Arrays.asList( + org.elasticsearch.core.List.of( new String[]{"org.elasticsearch.monitor.test", "methodOne"}, new String[]{"org.elasticsearch.monitor.testOther", "methodTwo"} )).toArray(new StackTraceElement[0]); StackTraceElement[] stackTwo = makeThreadStackHelper( - Arrays.asList( + org.elasticsearch.core.List.of( new String[]{"org.elasticsearch.monitor.test1", "methodOne"}, new String[]{"org.elasticsearch.monitor.testOther", "methodTwo"} )).toArray(new StackTraceElement[0]); StackTraceElement[] stackThree = makeThreadStackHelper( - Arrays.asList( + org.elasticsearch.core.List.of( new String[]{"org.elasticsearch.monitor.testOther", "methodTwo"}, new String[]{"org.elasticsearch.monitor.test", "methodOne"} )).toArray(new StackTraceElement[0]); StackTraceElement[] stackFour = makeThreadStackHelper( - Arrays.asList( + org.elasticsearch.core.List.of( new String[]{"org.elasticsearch.monitor.testPrior", "methodOther"}, new String[]{"org.elasticsearch.monitor.test", "methodOne"}, new String[]{"org.elasticsearch.monitor.testOther", "methodTwo"} @@ -205,7 +204,7 @@ private List makeThreadInfoMocksHelper(ThreadMXBean mockedMXBean, lo when(mockedThreadInfo.getThreadId()).thenReturn(threadId); StackTraceElement[] stack = makeThreadStackHelper( - Arrays.asList( + org.elasticsearch.core.List.of( new String[]{"org.elasticsearch.monitor.test", String.format(Locale.ROOT, "method_%d", (threadId) % 2)}, new String[]{"org.elasticsearch.monitor.testOther", "methodFinal"} )).toArray(new StackTraceElement[0]); @@ -226,12 +225,13 @@ public void testInnerDetect() throws Exception { when(mockedMXBean.getAllThreadIds()).thenReturn(threadIds); List allInfos = makeThreadInfoMocksHelper(mockedMXBean, threadIds); - List cpuOrderedInfos = Arrays.asList(allInfos.get(3), allInfos.get(2), allInfos.get(1), allInfos.get(0)); + List cpuOrderedInfos = org.elasticsearch.core.List.of( + allInfos.get(3), allInfos.get(2), allInfos.get(1), allInfos.get(0)); when(mockedMXBean.getThreadInfo(Matchers.any(), anyInt())).thenReturn(cpuOrderedInfos.toArray(new ThreadInfo[0])); HotThreads hotThreads = new HotThreads() .busiestThreads(4) - .type("cpu") + .type(HotThreads.ReportType.CPU) .interval(TimeValue.timeValueNanos(10)) .threadElementsSnapshotCount(11) .ignoreIdleThreads(false); @@ -259,13 +259,14 @@ public void testInnerDetect() throws Exception { HotThreads hotWaitingThreads = new HotThreads() .busiestThreads(4) - .type("wait") + .type(HotThreads.ReportType.WAIT) .interval(TimeValue.timeValueNanos(10)) .threadElementsSnapshotCount(11) .ignoreIdleThreads(false); allInfos = makeThreadInfoMocksHelper(mockedMXBean, threadIds); - List waitOrderedInfos = Arrays.asList(allInfos.get(3), allInfos.get(1), allInfos.get(0), allInfos.get(2)); + List waitOrderedInfos = org.elasticsearch.core.List.of( + allInfos.get(3), allInfos.get(1), allInfos.get(0), allInfos.get(2)); when(mockedMXBean.getThreadInfo(Matchers.any(), anyInt())).thenReturn(waitOrderedInfos.toArray(new ThreadInfo[0])); String waitInnerResult = hotWaitingThreads.innerDetect(mockedMXBean, mockCurrentThreadId); @@ -277,13 +278,14 @@ public void testInnerDetect() throws Exception { HotThreads hotBlockedThreads = new HotThreads() .busiestThreads(4) - .type("block") + .type(HotThreads.ReportType.BLOCK) .interval(TimeValue.timeValueNanos(10)) .threadElementsSnapshotCount(11) .ignoreIdleThreads(false); allInfos = makeThreadInfoMocksHelper(mockedMXBean, threadIds); - List blockOrderedInfos = Arrays.asList(allInfos.get(2), allInfos.get(0), allInfos.get(1), allInfos.get(3)); + List blockOrderedInfos = org.elasticsearch.core.List.of( + allInfos.get(2), allInfos.get(0), allInfos.get(1), allInfos.get(3)); when(mockedMXBean.getThreadInfo(Matchers.any(), anyInt())).thenReturn(blockOrderedInfos.toArray(new ThreadInfo[0])); String blockInnerResult = hotBlockedThreads.innerDetect(mockedMXBean, mockCurrentThreadId); @@ -308,7 +310,7 @@ public void testEnsureInnerDetectSkipsCurrentThread() throws Exception { HotThreads hotThreads = new HotThreads() .busiestThreads(4) - .type("cpu") + .type(HotThreads.ReportType.CPU) .interval(TimeValue.timeValueNanos(10)) .threadElementsSnapshotCount(11) .ignoreIdleThreads(false); @@ -317,4 +319,22 @@ public void testEnsureInnerDetectSkipsCurrentThread() throws Exception { assertEquals(1, innerResult.split("\r\n|\r|\n").length); } + + public void testReportTypeValueGetter() { + ThreadInfo mockedThreadInfo = mock(ThreadInfo.class); + + when(mockedThreadInfo.getBlockedTime()).thenReturn(2L).thenReturn(0L); + when(mockedThreadInfo.getWaitedTime()).thenReturn(3L).thenReturn(0L); + + HotThreads.ThreadTimeAccumulator info = new HotThreads.ThreadTimeAccumulator(mockedThreadInfo, 1L); + + assertEquals(1L, HotThreads.ThreadTimeAccumulator.valueGetterForReportType(HotThreads.ReportType.CPU).applyAsLong(info)); + assertEquals(3L, HotThreads.ThreadTimeAccumulator.valueGetterForReportType(HotThreads.ReportType.WAIT).applyAsLong(info)); + assertEquals(2L, HotThreads.ThreadTimeAccumulator.valueGetterForReportType(HotThreads.ReportType.BLOCK).applyAsLong(info)); + + //Ensure all enum types have a report type getter + for (HotThreads.ReportType type : HotThreads.ReportType.values()) { + assertNotNull(HotThreads.ThreadTimeAccumulator.valueGetterForReportType(type)); + } + } }