From 834f329eef4cbf45096bbffa7ba09194f797f693 Mon Sep 17 00:00:00 2001 From: Rajat Jain Date: Wed, 8 Jul 2026 14:12:40 +0530 Subject: [PATCH 1/2] Optimize parent controller path retrieval in JMeterThread --- .../apache/jmeter/threads/JMeterThread.java | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/core/src/main/java/org/apache/jmeter/threads/JMeterThread.java b/src/core/src/main/java/org/apache/jmeter/threads/JMeterThread.java index 35f94b26982..b36bef0abcc 100644 --- a/src/core/src/main/java/org/apache/jmeter/threads/JMeterThread.java +++ b/src/core/src/main/java/org/apache/jmeter/threads/JMeterThread.java @@ -19,7 +19,9 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; @@ -92,6 +94,8 @@ public class JMeterThread implements Runnable, Interruptible { private final HashTree testTree; + private final Map> parentControllersCache = new HashMap<>(); + private final TestCompiler compiler; private final JMeterThreadMonitor monitor; @@ -343,6 +347,20 @@ public void run() { } } + private static class CachedPathToRootTraverser extends FindTestElementsUpToRootTraverser { + private final List controllers; + + CachedPathToRootTraverser(List controllers) { + super(null); + this.controllers = controllers; + } + + @Override + public List getControllersToRoot() { + return controllers; + } + } + /** * Trigger break/continue/switch to next thread Loop depending on consumer implementation * @param sampler Sampler Base sampler @@ -363,8 +381,16 @@ private void triggerLoopLogicalActionOnParentControllers(Sampler sampler, JMeter (sampler != null ? sampler.getName() : "null") + ", sampler:" + sampler); } // Find parent controllers of current sampler - FindTestElementsUpToRootTraverser pathToRootTraverser = new FindTestElementsUpToRootTraverser(realSampler); - testTree.traverse(pathToRootTraverser); + List controllers = parentControllersCache.get(realSampler); + FindTestElementsUpToRootTraverser pathToRootTraverser; + if (controllers == null) { + pathToRootTraverser = new FindTestElementsUpToRootTraverser(realSampler); + testTree.traverse(pathToRootTraverser); + controllers = pathToRootTraverser.getControllersToRoot(); + parentControllersCache.put(realSampler, controllers); + } else { + pathToRootTraverser = new CachedPathToRootTraverser(controllers); + } consumer.accept(pathToRootTraverser); From 40a17a9bb138aedaf3fc8b46feb84d69d00255cb Mon Sep 17 00:00:00 2001 From: Rajat Jain Date: Fri, 17 Jul 2026 16:36:10 +0530 Subject: [PATCH 2/2] taking path-to-root controllers from SamplePackage. --- .../apache/jmeter/threads/JMeterThread.java | 58 +++++-------------- .../apache/jmeter/threads/SamplePackage.java | 9 +++ .../apache/jmeter/threads/TestCompiler.java | 13 +++++ 3 files changed, 36 insertions(+), 44 deletions(-) diff --git a/src/core/src/main/java/org/apache/jmeter/threads/JMeterThread.java b/src/core/src/main/java/org/apache/jmeter/threads/JMeterThread.java index b36bef0abcc..a116ca2681c 100644 --- a/src/core/src/main/java/org/apache/jmeter/threads/JMeterThread.java +++ b/src/core/src/main/java/org/apache/jmeter/threads/JMeterThread.java @@ -19,9 +19,7 @@ import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; @@ -94,8 +92,6 @@ public class JMeterThread implements Runnable, Interruptible { private final HashTree testTree; - private final Map> parentControllersCache = new HashMap<>(); - private final TestCompiler compiler; private final JMeterThreadMonitor monitor; @@ -347,28 +343,14 @@ public void run() { } } - private static class CachedPathToRootTraverser extends FindTestElementsUpToRootTraverser { - private final List controllers; - - CachedPathToRootTraverser(List controllers) { - super(null); - this.controllers = controllers; - } - - @Override - public List getControllersToRoot() { - return controllers; - } - } - /** * Trigger break/continue/switch to next thread Loop depending on consumer implementation * @param sampler Sampler Base sampler * @param threadContext - * @param consumer Consumer that will process the tree of elements up to root node + * @param consumer Consumer that will process the parent controllers list */ private void triggerLoopLogicalActionOnParentControllers(Sampler sampler, JMeterContext threadContext, - Consumer consumer) { + Consumer> consumer) { TransactionSampler transactionSampler = null; if (sampler instanceof TransactionSampler transSampler) { transactionSampler = transSampler; @@ -380,19 +362,10 @@ private void triggerLoopLogicalActionOnParentControllers(Sampler sampler, JMeter "Got null subSampler calling findRealSampler for:" + (sampler != null ? sampler.getName() : "null") + ", sampler:" + sampler); } - // Find parent controllers of current sampler - List controllers = parentControllersCache.get(realSampler); - FindTestElementsUpToRootTraverser pathToRootTraverser; - if (controllers == null) { - pathToRootTraverser = new FindTestElementsUpToRootTraverser(realSampler); - testTree.traverse(pathToRootTraverser); - controllers = pathToRootTraverser.getControllersToRoot(); - parentControllersCache.put(realSampler, controllers); - } else { - pathToRootTraverser = new CachedPathToRootTraverser(controllers); - } + // Reuse controller path already computed by TestCompiler + List controllers = compiler.getControllersForSampler(realSampler); - consumer.accept(pathToRootTraverser); + consumer.accept(controllers); // bug 52968 // When using Start Next Loop option combined to TransactionController. @@ -407,11 +380,10 @@ private void triggerLoopLogicalActionOnParentControllers(Sampler sampler, JMeter /** * Executes a continue of current loop, equivalent of "continue" in algorithm. * As a consequence it ends the first loop it finds on the path to root - * @param pathToRootTraverser {@link FindTestElementsUpToRootTraverser} + * @param controllers parent controllers from nearest to root */ - private static void continueOnCurrentLoop(FindTestElementsUpToRootTraverser pathToRootTraverser) { - List controllersToReinit = pathToRootTraverser.getControllersToRoot(); - for (Controller parentController : controllersToReinit) { + private static void continueOnCurrentLoop(List controllers) { + for (Controller parentController : controllers) { if (parentController instanceof AbstractThreadGroup tg) { tg.startNextLoop(); } else if (parentController instanceof IteratingController iterController) { @@ -426,11 +398,10 @@ private static void continueOnCurrentLoop(FindTestElementsUpToRootTraverser path /** * Executes a break of current loop, equivalent of "break" in algorithm. * As a consequence it ends the first loop it finds on the path to root - * @param pathToRootTraverser {@link FindTestElementsUpToRootTraverser} + * @param controllers parent controllers from nearest to root */ - private static void breakOnCurrentLoop(FindTestElementsUpToRootTraverser pathToRootTraverser) { - List controllersToReinit = pathToRootTraverser.getControllersToRoot(); - for (Controller parentController : controllersToReinit) { + private static void breakOnCurrentLoop(List controllers) { + for (Controller parentController : controllers) { if (parentController instanceof AbstractThreadGroup tg) { tg.breakThreadLoop(); } else if (parentController instanceof IteratingController iterController) { @@ -445,11 +416,10 @@ private static void breakOnCurrentLoop(FindTestElementsUpToRootTraverser pathToR /** * Executes a restart of Thread loop, equivalent of "continue" in algorithm but on Thread Loop. * As a consequence it ends all loop on the path to root - * @param pathToRootTraverser {@link FindTestElementsUpToRootTraverser} + * @param controllers parent controllers from nearest to root */ - private static void continueOnThreadLoop(FindTestElementsUpToRootTraverser pathToRootTraverser) { - List controllersToReinit = pathToRootTraverser.getControllersToRoot(); - for (Controller parentController : controllersToReinit) { + private static void continueOnThreadLoop(List controllers) { + for (Controller parentController : controllers) { if (parentController instanceof AbstractThreadGroup tg) { tg.startNextLoop(); } else { diff --git a/src/core/src/main/java/org/apache/jmeter/threads/SamplePackage.java b/src/core/src/main/java/org/apache/jmeter/threads/SamplePackage.java index 7265b5b7fbd..deb0b3e2f49 100644 --- a/src/core/src/main/java/org/apache/jmeter/threads/SamplePackage.java +++ b/src/core/src/main/java/org/apache/jmeter/threads/SamplePackage.java @@ -226,4 +226,13 @@ public List getConfigs() { return configs; } + /** + * Returns the parent controllers from nearest to root. + * + * @return List of {@link Controller} + */ + public List getControllers() { + return controllers; + } + } diff --git a/src/core/src/main/java/org/apache/jmeter/threads/TestCompiler.java b/src/core/src/main/java/org/apache/jmeter/threads/TestCompiler.java index 14b537a0e19..a3ca25517a8 100644 --- a/src/core/src/main/java/org/apache/jmeter/threads/TestCompiler.java +++ b/src/core/src/main/java/org/apache/jmeter/threads/TestCompiler.java @@ -114,6 +114,19 @@ public SamplePackage configureTransactionSampler(TransactionSampler transactionS return pack; } + /** + * Returns the parent controllers for the given sampler without + * triggering configuration side effects. + * + * @param sampler the {@link Sampler} to look up + * @return parent controllers from nearest to root, or an empty list + * if the sampler has not been compiled + */ + public List getControllersForSampler(Sampler sampler) { + SamplePackage pack = samplerConfigMap.get(sampler); + return pack != null ? pack.getControllers() : List.of(); + } + /** * Reset pack to its initial state * @param pack the {@link SamplePackage} to reset