diff --git a/README.md b/README.md index 709c8af..f527eb8 100644 --- a/README.md +++ b/README.md @@ -143,10 +143,10 @@ Java Mission Control provides an Automatic Analysis tool that performs pattern a It is possible to write assertions against the Automatic Analysis results to verify that unit tests against common performance issues: ```java -import dev.morling.jfrunit.*; +import org.moditect.jfrunit.*; -import static dev.morling.jfrunit.JfrEventsAssert.*; -import static dev.morling.jfrunit.ExpectedEvent.*; +import static org.moditect.jfrunit.JfrEventsAssert.*; +import static org.moditect.jfrunit.ExpectedEvent.*; @Test @EnableConfiguration("profile") diff --git a/src/main/java/org/moditect/jfrunit/JfrAnalysisAssert.java b/src/main/java/org/moditect/jfrunit/JfrAnalysisAssert.java deleted file mode 100644 index db8021b..0000000 --- a/src/main/java/org/moditect/jfrunit/JfrAnalysisAssert.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * Copyright 2020 - 2021 The JfrUnit authors. - * - * 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 org.moditect.jfrunit; - -import java.util.Optional; - -import org.assertj.core.api.AbstractAssert; -import org.openjdk.jmc.common.unit.IQuantity; -import org.openjdk.jmc.flightrecorder.rules.IResult; -import org.openjdk.jmc.flightrecorder.rules.IRule; -import org.openjdk.jmc.flightrecorder.rules.Severity; -import org.openjdk.jmc.flightrecorder.rules.TypedResult; - -public class JfrAnalysisAssert extends AbstractAssert { - - private IResult foundResult; - - public JfrAnalysisAssert(JfrAnalysisResults results) { - super(results, JfrAnalysisAssert.class); - } - - public static JfrAnalysisAssert assertThat(JfrAnalysisResults results) { - return new JfrAnalysisAssert(results); - } - - public JfrAnalysisAssert doesNotContain(Class expectedRule) { - return findRule(expectedRule, true, "JMC Analysis result contains rule of type <%s>"); - } - - public JfrAnalysisAssert contains(Class expectedRule) { - return findRule(expectedRule); - } - - private JfrAnalysisAssert findRule(Class expectedRule) { - return findRule(expectedRule, false, "No JMC Analysis result rule of type <%s>"); - } - - private JfrAnalysisAssert findRule(Class expectedRule, boolean negate, String failureMsg) { - isNotNull(); - - Optional optionalIResult = actual.getResults().stream() - .filter(re -> re.getRule().getClass().equals(expectedRule)) - .findAny(); - - boolean found = optionalIResult - .isPresent(); - - if (negate ? found : !found) { - failWithMessage(failureMsg, expectedRule.getName()); - } - else { - if (!negate) { - this.foundResult = optionalIResult.get(); - } - } - - return this; - - } - - public JfrAnalysisAssert hasSeverity(Class expectedRule, Severity expectedSeverity) { - Optional resultOptional = findResult(expectedRule); - - if (!resultOptional.isPresent()) { - failWithMessage("No analysis type for <%s>", expectedRule.getName()); - } - else { - IResult result = resultOptional.get(); - - if (result.getSeverity().getLimit() < expectedSeverity.getLimit()) { - failWithMessage("Analysis result not required severity <%s>", expectedSeverity); - - } - } - return this; - } - - public JfrAnalysisAssert scoresLessThan(Class expectedRule, double expectedScore) { - - findRule(expectedRule); - - return scoresLessThan(expectedScore); - } - - public JfrAnalysisAssert scoresLessThan(double expectedScore) { - IQuantity resultScore = this.foundResult.getResult(TypedResult.SCORE); - double score = 0; - if (resultScore != null) { - score = resultScore.doubleValue(); - } - else if (this.foundResult.getSeverity().getLimit() != 0.0d) { - score = this.foundResult.getSeverity().getLimit(); - } - - if (score > expectedScore) { - failWithMessage("Analysis result score exceeds threshold: actual <%.1f>, threshold <%.1f>", score, expectedScore); - } - return this; - - } - - private Optional findResult(Class expectedRule) { - return actual.getResults().stream() - .filter(re -> re.getRule().getClass().equals(expectedRule)) - .findFirst(); - - } -} diff --git a/src/main/java/org/moditect/jfrunit/JfrAnalysisResults.java b/src/main/java/org/moditect/jfrunit/JfrAnalysisResults.java deleted file mode 100644 index 2b145ba..0000000 --- a/src/main/java/org/moditect/jfrunit/JfrAnalysisResults.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * Copyright 2020 - 2021 The JfrUnit authors. - * - * 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 org.moditect.jfrunit; - -import java.util.List; - -import org.openjdk.jmc.flightrecorder.rules.IResult; - -public class JfrAnalysisResults { - private List results; - - public JfrAnalysisResults(List analysisRecording) { - this.results = analysisRecording; - } - - public List getResults() { - return this.results; - } - - public int size() { - return this.results.size(); - } -} diff --git a/src/main/java/org/moditect/jfrunit/JfrEvents.java b/src/main/java/org/moditect/jfrunit/JfrEvents.java index 3ed21bf..7d9107f 100644 --- a/src/main/java/org/moditect/jfrunit/JfrEvents.java +++ b/src/main/java/org/moditect/jfrunit/JfrEvents.java @@ -32,13 +32,16 @@ import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.stream.Stream; import org.moditect.jfrunit.EnableEvent.StacktracePolicy; +import org.moditect.jfrunit.internal.JmcAutomaticAnalysis; import org.moditect.jfrunit.internal.SyncEvent; +import org.openjdk.jmc.flightrecorder.rules.IResult; import org.openjdk.jmc.flightrecorder.rules.Severity; import jdk.jfr.Configuration; @@ -64,7 +67,7 @@ public class JfrEvents { private Recording recording; private boolean capturing; - private JfrAnalysisResults analysis = null; + private AtomicInteger analysisCounter = new AtomicInteger(0); public JfrEvents() { } @@ -138,6 +141,10 @@ void stopRecordingEvents() { } private Path getRecordingFilePath() throws URISyntaxException, IOException { + return getRecordingFilePath(null); + } + + private Path getRecordingFilePath(String suffix) throws URISyntaxException, IOException { URI testSourceUri = testMethod.getDeclaringClass().getProtectionDomain().getCodeSource().getLocation().toURI(); Path dumpDir; try { @@ -148,7 +155,7 @@ private Path getRecordingFilePath() throws URISyntaxException, IOException { dumpDir = Files.createTempDirectory(null); LOGGER.log(Level.WARNING, "'" + testSourceUri.getScheme() + "' is not a valid file system, dumping recording to a temporary location."); } - String fileName = getDumpFileName(); + String fileName = getDumpFileName(suffix); return dumpDir.resolve(fileName); } @@ -314,9 +321,9 @@ private List matchEventTypes(List enable return allEvents; } - private String getDumpFileName() { + private String getDumpFileName(String suffix) { if (dumpFileName == null) { - return getDefaultDumpFileName(); + return getDefaultDumpFileName(suffix); } else { return dumpFileName.endsWith(".jfr") ? dumpFileName : dumpFileName + ".jfr"; @@ -324,21 +331,29 @@ private String getDumpFileName() { } private String getDefaultDumpFileName() { - return testMethod.getDeclaringClass().getName() + "-" + testMethod.getName() + ".jfr"; + return getDefaultDumpFileName(null); } - public JfrAnalysisResults automaticAnalysis() { - if (analysis == null) { - try { - Path recordingPath = getRecordingFilePath(); - dumpRecording(recordingPath); + private String getDefaultDumpFileName(String suffix) { + return testMethod.getDeclaringClass().getName() + "-" + testMethod.getName() + (suffix != null ? "-" + suffix : "") + ".jfr"; + } - analysis = new JfrAnalysisResults(JfrAnalysis.analysisRecording(recordingPath.toAbsolutePath().toString(), Severity.INFO)); - } - catch (IOException | URISyntaxException e) { - LOGGER.log(Level.WARNING, "Unable to analyse jfr recording: " + e.getLocalizedMessage()); - } + // TODO: Do we move out of JfrEvents? + public List automaticAnalysis() { + try { + awaitEvents(); + + int counter = analysisCounter.getAndIncrement(); + Path recordingPath = getRecordingFilePath("analysis" + (counter != 0 ? "-" + counter : "")); + + LOGGER.log(Level.INFO, "Analysis recording: " + recordingPath.toAbsolutePath()); + dumpRecording(recordingPath); + + return JmcAutomaticAnalysis.analysisRecording(recordingPath.toAbsolutePath().toString(), Severity.INFO); + + } + catch (IOException | URISyntaxException e) { + throw new RuntimeException("Unable to analyse jfr recording", e); } - return analysis; } } diff --git a/src/main/java/org/moditect/jfrunit/JmcAutomaticAnalysisAssert.java b/src/main/java/org/moditect/jfrunit/JmcAutomaticAnalysisAssert.java new file mode 100644 index 0000000..be774ed --- /dev/null +++ b/src/main/java/org/moditect/jfrunit/JmcAutomaticAnalysisAssert.java @@ -0,0 +1,171 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright 2020 - 2021 The JfrUnit authors. + * + * 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 org.moditect.jfrunit; + +import java.util.List; +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.stream.Collectors; + +import org.assertj.core.api.AbstractAssert; +import org.openjdk.jmc.common.unit.IQuantity; +import org.openjdk.jmc.flightrecorder.rules.IResult; +import org.openjdk.jmc.flightrecorder.rules.IRule; +import org.openjdk.jmc.flightrecorder.rules.Severity; +import org.openjdk.jmc.flightrecorder.rules.TypedResult; + +public class JmcAutomaticAnalysisAssert extends AbstractAssert> { + + private IResult foundResult; + private static final String LS = System.getProperty("line.separator"); + + public JmcAutomaticAnalysisAssert(List results) { + super(results, JmcAutomaticAnalysisAssert.class); + } + + public static JmcAutomaticAnalysisAssert assertThat(List results) { + return new JmcAutomaticAnalysisAssert(results); + } + + public JmcAutomaticAnalysisAssert doesNotContain(Class expectedRule) { + return findRule(expectedRule, true, "JMC Analysis result contains rule of type <%s>"); + } + + public JmcAutomaticAnalysisAssert contains(Class expectedRule) { + return findRule(expectedRule); + } + + private JmcAutomaticAnalysisAssert findRule(Class expectedRule) { + return findRule(expectedRule, false, "No JMC Analysis result rule of type <%s>"); + } + + private JmcAutomaticAnalysisAssert findRule(Class expectedRule, boolean negate, String failureMsg) { + isNotNull(); + + Optional optionalIResult = actual.stream() + .filter(re -> re.getRule().getClass().equals(expectedRule)) + .findAny(); + + boolean found = optionalIResult + .isPresent(); + + if (negate ? found : !found) { + failWithMessage(failureMsg, expectedRule.getName()); + } + else { + if (!negate) { + this.foundResult = optionalIResult.get(); + } + } + + return this; + + } + + public JmcAutomaticAnalysisAssert hasSeverity(Class expectedRule, Severity expectedSeverity) { + Optional resultOptional = findResult(expectedRule); + + if (!resultOptional.isPresent()) { + failWithMessage("No analysis type for <%s>", expectedRule.getName()); + } + else { + IResult result = resultOptional.get(); + + if (result.getSeverity().getLimit() < expectedSeverity.getLimit()) { + failWithMessage("Analysis result not required severity <%s>", expectedSeverity); + + } + } + return this; + } + + public JmcAutomaticAnalysisAssert scoresLessThan(Class expectedRule, double expectedScore) { + + findRule(expectedRule); + + return scoresLessThan(expectedScore); + } + + public JmcAutomaticAnalysisAssert scoresLessThan(double expectedScore) { + IQuantity resultScore = this.foundResult.getResult(TypedResult.SCORE); + double score = 0; + if (resultScore != null) { + score = resultScore.doubleValue(); + } + else if (this.foundResult.getSeverity().getLimit() != 0.0d) { + score = this.foundResult.getSeverity().getLimit(); + } + + if (score > expectedScore) { + failWithMessage("Analysis result score exceeds threshold: actual <%.1f>, threshold <%.1f>", score, expectedScore); + } + return this; + + } + + public JmcAutomaticAnalysisAssert removeRuleFromResults(Class rule) { + actual + .stream() + .filter(result -> result.getRule().equals(rule)).forEach(filtered -> actual.remove(filtered)); + return this; + } + + public JmcAutomaticAnalysisAssert haveSeverityGreaterThan(Severity expectedSeverity) { + List filterResults = filterBySeverity(expectedSeverity, (expected, actualSeverity) -> actualSeverity.compareTo(expectedSeverity) < 0); + if (filterResults.size() == 0) { + failWithMessage("Expected to contain severity greater than: " + expectedSeverity.getLocalizedName()); + } + return this; + } + + public JmcAutomaticAnalysisAssert haveSeverityLessThan(Severity expectedSeverity) { + List filterResults = filterBySeverity(expectedSeverity, (expected, actualSeverity) -> actualSeverity.compareTo(expectedSeverity) >= 0); + + if (filterResults.size() > 0) { + StringBuilder reportBuilder = new StringBuilder(); + reportBuilder.append("Analysis result score equals or exceeds threshold: ") + .append(expectedSeverity.getLocalizedName()) + .append(LS).append(LS); + + filterResults.forEach(result -> { + reportBuilder.append(result.getSummary()) + .append(LS) + .append(result.getExplanation()) + .append(LS).append(LS); + }); + failWithMessage(reportBuilder.toString()); + } + return this; + + } + + private List filterBySeverity(Severity expectedSeverity, BiFunction severityComparator) { + return actual + .stream() + .filter(result -> severityComparator.apply(expectedSeverity, result.getSeverity())) + .collect(Collectors.toList()); + } + + private Optional findResult(Class expectedRule) { + return actual.stream() + .filter(re -> re.getRule().getClass().equals(expectedRule)) + .findFirst(); + + } +} diff --git a/src/main/java/org/moditect/jfrunit/JfrAnalysis.java b/src/main/java/org/moditect/jfrunit/internal/JmcAutomaticAnalysis.java similarity index 58% rename from src/main/java/org/moditect/jfrunit/JfrAnalysis.java rename to src/main/java/org/moditect/jfrunit/internal/JmcAutomaticAnalysis.java index 3c8d7ae..dd1fdae 100644 --- a/src/main/java/org/moditect/jfrunit/JfrAnalysis.java +++ b/src/main/java/org/moditect/jfrunit/internal/JmcAutomaticAnalysis.java @@ -15,7 +15,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.moditect.jfrunit; +package org.moditect.jfrunit.internal; import java.io.File; import java.io.IOException; @@ -31,15 +31,11 @@ import org.openjdk.jmc.flightrecorder.rules.Severity; import org.openjdk.jmc.flightrecorder.rules.util.RulesToolkit; -public class JfrAnalysis { +public class JmcAutomaticAnalysis { - private static final System.Logger LOGGER = System.getLogger(JfrAnalysis.class.getName()); + private static final System.Logger LOGGER = System.getLogger(JmcAutomaticAnalysis.class.getName()); public static List analysisRecording(String fileName, Severity minSeverity) { - return JfrAnalysis.analysisRecording(fileName, minSeverity, false); - } - - public static List analysisRecording(String fileName, Severity minSeverity, boolean verbose) { try { File file = new File(fileName); @@ -51,35 +47,40 @@ public static List analysisRecording(String fileName, Severity minSever LOGGER.log(System.Logger.Level.ERROR, "Unable to analyse jfr recording: " + e.getLocalizedMessage()); return null; } + return analyseEvents(events, minSeverity); - // TODO: Provide configuration - Map> resultFutures = RulesToolkit.evaluateParallel(RuleRegistry.getRules(), events, - null, 0); - List>> resultFutureList = new ArrayList<>(resultFutures.entrySet()); - Collections.sort(resultFutureList, Comparator.comparing(o -> o.getKey().getId())); - - List analysisResults = new ArrayList(); - - for (Map.Entry> resultEntry : resultFutureList) { - IResult result; - try { - result = resultEntry.getValue().get(); - } - catch (Throwable t) { - LOGGER.log(System.Logger.Level.WARNING, "Unable to analyse analysis result: " + t.getLocalizedMessage()); - continue; - } - - if (result != null && result.getSeverity().compareTo(minSeverity) >= 0) { - // TODO: further results processing - analysisResults.add(result); - } - } - return analysisResults; } catch (Throwable t) { System.err.println("Got exception when creating report for " + fileName); //$NON-NLS-1$ throw t; } } + + public static List analyseEvents(IItemCollection events, Severity minSeverity) { + // TODO: Provide configuration + Map> resultFutures = RulesToolkit.evaluateParallel(RuleRegistry.getRules(), events, + null, 0); + List>> resultFutureList = new ArrayList<>(resultFutures.entrySet()); + Collections.sort(resultFutureList, Comparator.comparing(o -> o.getKey().getId())); + + List analysisResults = new ArrayList(); + + for (Map.Entry> resultEntry : resultFutureList) { + IResult result; + try { + result = resultEntry.getValue().get(); + } + catch (Throwable t) { + LOGGER.log(System.Logger.Level.WARNING, "Unable to analyse analysis result: " + t.getLocalizedMessage()); + continue; + } + + if (result != null && result.getSeverity().compareTo(minSeverity) >= 0) { + analysisResults.add(result); + } + } + return analysisResults; + + } + } diff --git a/src/test/java/org/moditect/jfrunit/JfrUnitTest.java b/src/test/java/org/moditect/jfrunit/JfrUnitTest.java index 06fdc8e..5475cd1 100644 --- a/src/test/java/org/moditect/jfrunit/JfrUnitTest.java +++ b/src/test/java/org/moditect/jfrunit/JfrUnitTest.java @@ -18,18 +18,12 @@ package org.moditect.jfrunit; import java.time.Duration; -import java.util.stream.Collectors; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.openjdk.jmc.flightrecorder.rules.Severity; -import org.openjdk.jmc.flightrecorder.rules.jdk.memory.FullGcRule; -import org.openjdk.jmc.flightrecorder.rules.jdk.memory.HeapDumpRule; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.moditect.jfrunit.ExpectedEvent.event; -import static org.moditect.jfrunit.JfrAnalysisAssert.assertThat; import static org.moditect.jfrunit.JfrEventsAssert.assertThat; @JfrEventTest @@ -72,7 +66,7 @@ public void shouldHaveGcAndSleepEventsWithDefaultConfiguration() throws Exceptio event("jdk.ThreadSleep").with("time", Duration.ofMillis(50))); assertThat(jfrEvents.filter( - event("jdk.GarbageCollection").with("cause", "System.gc()")).collect(Collectors.toList())) + event("jdk.GarbageCollection").with("cause", "System.gc()"))) .hasSize(1); } @@ -145,32 +139,4 @@ public void doNotCaptureTraceWhenDisabledWithStackTracePolicyExcluded() { assertThat(jfrEvents).contains(event("jfrunit.test.StackTraceDisabledSampleEvent").hasNot("stackTrace")); } - @Test - @EnableConfiguration("profile") - public void automatedAnalysis() throws Exception { - - System.gc(); - Thread.sleep(1000); - - jfrEvents.awaitEvents(); - - JfrAnalysisResults analysisResults = jfrEvents.automaticAnalysis(); - - assertNotNull(analysisResults); - - assertThat(analysisResults.size()).isGreaterThan(0); - - // Inspect rules that fired - assertThat(analysisResults).contains(FullGcRule.class); - assertThat(analysisResults).doesNotContain(HeapDumpRule.class); - - // Inspect severity of rule - assertThat(analysisResults).hasSeverity(FullGcRule.class, Severity.WARNING); - - // Inspect score of rule - assertThat(analysisResults) - .contains(FullGcRule.class) - .scoresLessThan(80); - - } } diff --git a/src/test/java/org/moditect/jfrunit/JmcAutomaticAnalysisTest.java b/src/test/java/org/moditect/jfrunit/JmcAutomaticAnalysisTest.java new file mode 100644 index 0000000..226093c --- /dev/null +++ b/src/test/java/org/moditect/jfrunit/JmcAutomaticAnalysisTest.java @@ -0,0 +1,92 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright 2020 - 2021 The JfrUnit authors. + * + * 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 org.moditect.jfrunit; + +import java.util.List; + +import javax.el.MethodNotFoundException; + +import org.junit.jupiter.api.Test; +import org.openjdk.jmc.flightrecorder.rules.IResult; +import org.openjdk.jmc.flightrecorder.rules.Severity; +import org.openjdk.jmc.flightrecorder.rules.jdk.general.StackDepthSettingRule; +import org.openjdk.jmc.flightrecorder.rules.jdk.memory.FullGcRule; +import org.openjdk.jmc.flightrecorder.rules.jdk.memory.HeapDumpRule; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@JfrEventTest +public class JmcAutomaticAnalysisTest { + + public JfrEvents jfrEvents = new JfrEvents(); + + @Test + @EnableConfiguration("profile") + public void automatedAnalysis() throws Exception { + + System.gc(); + Thread.sleep(1000); + + jfrEvents.awaitEvents(); + + List analysisResults = jfrEvents.automaticAnalysis(); + + assertNotNull(analysisResults); + + assertThat(analysisResults.size()).isGreaterThan(0); + + // Inspect rules that fired + JmcAutomaticAnalysisAssert.assertThat(analysisResults).contains(FullGcRule.class); + JmcAutomaticAnalysisAssert.assertThat(analysisResults).doesNotContain(HeapDumpRule.class); + + // Inspect severity of rule + JmcAutomaticAnalysisAssert.assertThat(analysisResults).hasSeverity(FullGcRule.class, Severity.WARNING); + + // Inspect score of rule + JmcAutomaticAnalysisAssert.assertThat(analysisResults) + .contains(FullGcRule.class) + .scoresLessThan(80); + + } + + @Test + @EnableConfiguration("profile") + public void automatedExceptionAnalysis() throws Exception { + + for (int i = 0; i < 20_000; i++) { + try { + throw new MethodNotFoundException(); + } + catch (MethodNotFoundException methodNotFoundException) { + // silently swallow exception + } + } + + List analysisResults = jfrEvents.automaticAnalysis(); + + assertThat(analysisResults.size()).isGreaterThan(0); + + JmcAutomaticAnalysisAssert.assertThat(analysisResults) + .removeRuleFromResults(StackDepthSettingRule.class) + .haveSeverityGreaterThan(Severity.WARNING); + + } + +}