From f719708fddfce827c563cf17f55441b4a2d8fdfa Mon Sep 17 00:00:00 2001 From: KingRabbid <13966341+KingRabbid@users.noreply.github.com> Date: Mon, 6 Oct 2025 14:33:52 +0300 Subject: [PATCH 1/6] Allow more complex configuration of the cache; generate the usage report at the end of run --- bin/jmeter.properties | 3 +++ .../apache/jmeter/util/JSR223TestElement.java | 16 ++++++++-------- xdocs/usermanual/component_reference.xml | 2 +- xdocs/usermanual/properties_reference.xml | 4 ++++ 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/bin/jmeter.properties b/bin/jmeter.properties index bac04e85bc9..05235ccd8f6 100644 --- a/bin/jmeter.properties +++ b/bin/jmeter.properties @@ -1235,6 +1235,9 @@ view.results.tree.renderers_order=.RenderAsText,.RenderAsRegexp,.RenderAsBoundar # Used by JSR-223 elements # Size of compiled scripts cache #jsr223.compiled_scripts_cache_size=100 +# Configurable options on the compiled scripts cache, overrides the jsr223.compiled_scripts_cache_size property +# See com.github.benmanes.caffeine.cache.Caffeine API for details +jsr223.compiled_scripts_cache_spec=maximumSize=100,recordStats #--------------------------------------------------------------------------- # Classpath configuration diff --git a/src/core/src/main/java/org/apache/jmeter/util/JSR223TestElement.java b/src/core/src/main/java/org/apache/jmeter/util/JSR223TestElement.java index b46b555d152..b44cb112438 100644 --- a/src/core/src/main/java/org/apache/jmeter/util/JSR223TestElement.java +++ b/src/core/src/main/java/org/apache/jmeter/util/JSR223TestElement.java @@ -60,10 +60,8 @@ public abstract class JSR223TestElement extends ScriptingTestElement * Cache of compiled scripts */ private static final Cache COMPILED_SCRIPT_CACHE = - Caffeine - .newBuilder() - .maximumSize(JMeterUtils.getPropDefault("jsr223.compiled_scripts_cache_size", 100)) - .build(); + Caffeine.from(JMeterUtils.getPropDefault("jsr223.compiled_scripts_cache_spec","maximumSize=" + + JMeterUtils.getPropDefault("jsr223.compiled_scripts_cache_size", 100) + ",recordStats")).build(); /** * Lambdas can't throw checked exceptions, so we wrap cache loading failure with a runtime one. @@ -257,11 +255,11 @@ private static CompiledScript getCompiledScript( } catch (ScriptCompilationInvocationTargetException e) { Throwable cause = e.getCause(); if (cause instanceof IOException) { - cause.addSuppressed(new IllegalStateException("Unable to compile script " + newCacheKey)); + cause.addSuppressed(new IllegalStateException("Unable to compile script: " + newCacheKey)); throw (IOException) cause; } if (cause instanceof ScriptException) { - cause.addSuppressed(new IllegalStateException("Unable to compile script " + newCacheKey)); + cause.addSuppressed(new IllegalStateException("Unable to compile script: " + newCacheKey)); throw (ScriptException) cause; } throw e; @@ -287,7 +285,7 @@ public boolean compile() ((Compilable) scriptEngine).compile(getScript()); return true; } catch (ScriptException e) { // NOSONAR - logger.error("Error compiling script for test element {}, error:{}", getName(), e.getMessage()); + logger.error("Error compiling script for test element named: '{}', error: {}", getName(), e.getMessage()); return false; } } else { @@ -297,7 +295,7 @@ public boolean compile() ((Compilable) scriptEngine).compile(fileReader); return true; } catch (ScriptException e) { // NOSONAR - logger.error("Error compiling script for test element {}, error:{}", getName(), e.getMessage()); + logger.error("Error compiling script for test element named: '{}', error: {}", getName(), e.getMessage()); return false; } } @@ -357,6 +355,8 @@ public void testEnded() { */ @Override public void testEnded(String host) { + if (COMPILED_SCRIPT_CACHE.estimatedSize() > 0) + logger.info("Compiled cache size: {}, stats: {}", COMPILED_SCRIPT_CACHE.estimatedSize(), COMPILED_SCRIPT_CACHE.stats()); COMPILED_SCRIPT_CACHE.invalidateAll(); scriptMd5 = null; } diff --git a/xdocs/usermanual/component_reference.xml b/xdocs/usermanual/component_reference.xml index 213a62758ed..dd27b725128 100644 --- a/xdocs/usermanual/component_reference.xml +++ b/xdocs/usermanual/component_reference.xml @@ -1185,7 +1185,7 @@ To benefit from this feature: Cache size is controlled by the following JMeter property (jmeter.properties): -jsr223.compiled_scripts_cache_size=100 + jsr223.compiled_scripts_cache_size=100 or via a more complex cache setup using the jsr223.compiled_scripts_cache_spec Unlike the , the interpreter is not saved between invocations. JSR223 Test Elements using Script file or Script text + checked Cache compiled script if available are now compiled if ScriptEngine supports this feature, this enables great performance enhancements. diff --git a/xdocs/usermanual/properties_reference.xml b/xdocs/usermanual/properties_reference.xml index d74ece0613d..c1508fb19a9 100644 --- a/xdocs/usermanual/properties_reference.xml +++ b/xdocs/usermanual/properties_reference.xml @@ -1985,6 +1985,10 @@ JMETER-SERVER Used by JSR-223 elements.
Size of compiled scripts cache.
Defaults to: 100 + + Used by JSR-223 elements.
+ Caffeine framework spec configuration in String format. Overrides jsr223.compiled_scripts_cache_size
+ Defaults to: maximumSize=,recordStats
From 1c82baa8625027054c2630085fb3c20b0992f275 Mon Sep 17 00:00:00 2001 From: KingRabbid <13966341+KingRabbid@users.noreply.github.com> Date: Mon, 6 Oct 2025 23:30:03 +0300 Subject: [PATCH 2/6] Unify the JSR223 logging error statements --- .../apache/jmeter/assertions/BSFAssertion.java | 2 +- .../jmeter/assertions/JSR223Assertion.java | 2 +- .../jmeter/extractor/BSFPostProcessor.java | 2 +- .../jmeter/extractor/JSR223PostProcessor.java | 2 +- .../apache/jmeter/modifiers/BSFPreProcessor.java | 2 +- .../jmeter/modifiers/BeanShellPreProcessor.java | 2 +- .../jmeter/modifiers/JSR223PreProcessor.java | 2 +- .../java/org/apache/jmeter/timers/BSFTimer.java | 2 +- .../org/apache/jmeter/timers/BeanShellTimer.java | 2 +- .../org/apache/jmeter/timers/JSR223Timer.java | 2 +- .../jmeter/visualizers/JSR223Listener.java | 2 +- .../gui/action/CompileJSR223TestElements.java | 6 +++--- .../apache/jmeter/util/JSR223TestElement.java | 16 ++++++++-------- .../java/org/apache/jmeter/functions/Groovy.java | 2 +- .../protocol/java/sampler/JSR223Sampler.java | 2 +- 15 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/components/src/main/java/org/apache/jmeter/assertions/BSFAssertion.java b/src/components/src/main/java/org/apache/jmeter/assertions/BSFAssertion.java index 57ea8ae84cb..0358c6179c8 100644 --- a/src/components/src/main/java/org/apache/jmeter/assertions/BSFAssertion.java +++ b/src/components/src/main/java/org/apache/jmeter/assertions/BSFAssertion.java @@ -42,7 +42,7 @@ public AssertionResult getResult(SampleResult response) { processFileOrScript(mgr); result.setError(false); } catch (BSFException e) { - log.warn("Problem in BSF script", e); + log.warn("Problem in BSF element named: '{}'", getName(), e); result.setFailure(true); result.setError(true); result.setFailureMessage(e.toString()); diff --git a/src/components/src/main/java/org/apache/jmeter/assertions/JSR223Assertion.java b/src/components/src/main/java/org/apache/jmeter/assertions/JSR223Assertion.java index ffffe0de725..7ffb8acdbaf 100644 --- a/src/components/src/main/java/org/apache/jmeter/assertions/JSR223Assertion.java +++ b/src/components/src/main/java/org/apache/jmeter/assertions/JSR223Assertion.java @@ -50,7 +50,7 @@ public AssertionResult getResult(SampleResult response) { processFileOrScript(scriptEngine, bindings); result.setError(false); } catch (IOException | ScriptException e) { - log.error("Problem in JSR223 script: {}", getName(), e); + log.error("Problem in JSR223 element named: '{}'", getName(), e); result.setError(true); result.setFailureMessage(e.toString()); } diff --git a/src/components/src/main/java/org/apache/jmeter/extractor/BSFPostProcessor.java b/src/components/src/main/java/org/apache/jmeter/extractor/BSFPostProcessor.java index 323c01daeb7..1fe97de5925 100644 --- a/src/components/src/main/java/org/apache/jmeter/extractor/BSFPostProcessor.java +++ b/src/components/src/main/java/org/apache/jmeter/extractor/BSFPostProcessor.java @@ -39,7 +39,7 @@ public void process(){ processFileOrScript(mgr); } catch (BSFException e) { if (log.isWarnEnabled()) { - log.warn("Problem in BSF script: {}", e.toString()); + log.warn("Problem in BSF element named: '{}'", getName(), e); } } finally { if (mgr != null) { diff --git a/src/components/src/main/java/org/apache/jmeter/extractor/JSR223PostProcessor.java b/src/components/src/main/java/org/apache/jmeter/extractor/JSR223PostProcessor.java index 1f3d51ae660..2eadc59f1ff 100644 --- a/src/components/src/main/java/org/apache/jmeter/extractor/JSR223PostProcessor.java +++ b/src/components/src/main/java/org/apache/jmeter/extractor/JSR223PostProcessor.java @@ -44,7 +44,7 @@ public void process() { ScriptEngine scriptEngine = getScriptEngine(); processFileOrScript(scriptEngine, null); } catch (ScriptException | IOException e) { - log.error("Problem in JSR223 script, {}", getName(), e); + log.error("Problem in JSR223 element named: '{}'", getName(), e); } } diff --git a/src/components/src/main/java/org/apache/jmeter/modifiers/BSFPreProcessor.java b/src/components/src/main/java/org/apache/jmeter/modifiers/BSFPreProcessor.java index 0e8a4a7e1ce..0c9d8b21158 100644 --- a/src/components/src/main/java/org/apache/jmeter/modifiers/BSFPreProcessor.java +++ b/src/components/src/main/java/org/apache/jmeter/modifiers/BSFPreProcessor.java @@ -42,7 +42,7 @@ public void process(){ processFileOrScript(mgr); } catch (BSFException e) { if (log.isWarnEnabled()) { - log.warn("Problem in BSF script. {}", e.toString()); + log.warn("Problem in BSF element named: '{}'", getName(), e); } } finally { if (mgr != null) { diff --git a/src/components/src/main/java/org/apache/jmeter/modifiers/BeanShellPreProcessor.java b/src/components/src/main/java/org/apache/jmeter/modifiers/BeanShellPreProcessor.java index f01991dd657..1a56a4d223a 100644 --- a/src/components/src/main/java/org/apache/jmeter/modifiers/BeanShellPreProcessor.java +++ b/src/components/src/main/java/org/apache/jmeter/modifiers/BeanShellPreProcessor.java @@ -62,7 +62,7 @@ public void process(){ processFileOrScript(bshInterpreter); } catch (JMeterException e) { if (log.isWarnEnabled()) { - log.warn("Problem in BeanShell script. {}", e.toString()); + log.warn("Problem in BeanShell element named: '{}'", getName(), e); } } } diff --git a/src/components/src/main/java/org/apache/jmeter/modifiers/JSR223PreProcessor.java b/src/components/src/main/java/org/apache/jmeter/modifiers/JSR223PreProcessor.java index 1d2b5132ce4..690ebede485 100644 --- a/src/components/src/main/java/org/apache/jmeter/modifiers/JSR223PreProcessor.java +++ b/src/components/src/main/java/org/apache/jmeter/modifiers/JSR223PreProcessor.java @@ -44,7 +44,7 @@ public void process() { ScriptEngine scriptEngine = getScriptEngine(); processFileOrScript(scriptEngine, null); } catch (ScriptException | IOException e) { - log.error("Problem in JSR223 script, {}", getName(), e); + log.error("Problem in JSR223 element named: '{}'", getName(), e); } } diff --git a/src/components/src/main/java/org/apache/jmeter/timers/BSFTimer.java b/src/components/src/main/java/org/apache/jmeter/timers/BSFTimer.java index ae324dba7bf..c4abd9a16a6 100644 --- a/src/components/src/main/java/org/apache/jmeter/timers/BSFTimer.java +++ b/src/components/src/main/java/org/apache/jmeter/timers/BSFTimer.java @@ -44,7 +44,7 @@ public long delay() { delay = Long.parseLong(o.toString()); } catch (NumberFormatException | BSFException e) { if (log.isWarnEnabled()) { - log.warn("Problem in BSF script. {}", e.toString()); + log.warn("Problem in BSF element named: '{}'", getName(), e); } } finally { if(mgr != null) { diff --git a/src/components/src/main/java/org/apache/jmeter/timers/BeanShellTimer.java b/src/components/src/main/java/org/apache/jmeter/timers/BeanShellTimer.java index c383f2c49b4..9da5a2fa45a 100644 --- a/src/components/src/main/java/org/apache/jmeter/timers/BeanShellTimer.java +++ b/src/components/src/main/java/org/apache/jmeter/timers/BeanShellTimer.java @@ -59,7 +59,7 @@ public long delay() { } } catch (JMeterException e) { if (log.isWarnEnabled()) { - log.warn("Problem in BeanShell script. {}", e.toString()); + log.warn("Problem in BeanShell element named: '{}'", getName(), e); } } try { diff --git a/src/components/src/main/java/org/apache/jmeter/timers/JSR223Timer.java b/src/components/src/main/java/org/apache/jmeter/timers/JSR223Timer.java index 6ae5ef2fd4c..36455d575d4 100644 --- a/src/components/src/main/java/org/apache/jmeter/timers/JSR223Timer.java +++ b/src/components/src/main/java/org/apache/jmeter/timers/JSR223Timer.java @@ -47,7 +47,7 @@ public long delay() { } delay = Long.parseLong(o.toString()); } catch (NumberFormatException | IOException | ScriptException e) { - log.error("Problem in JSR223 script, {}", getName(), e); + log.error("Problem in JSR223 element named: '{}'", getName(), e); } return delay; } diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/JSR223Listener.java b/src/components/src/main/java/org/apache/jmeter/visualizers/JSR223Listener.java index ac066f6d7bd..3d795ac8ebb 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/JSR223Listener.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/JSR223Listener.java @@ -53,7 +53,7 @@ public void sampleOccurred(SampleEvent event) { bindings.put("sampleResult", event.getResult()); processFileOrScript(scriptEngine, bindings); } catch (ScriptException | IOException e) { - log.error("Problem in JSR223 script, {}", getName(), e); + log.error("Problem in JSR223 element named: '{}'", getName(), e); } } diff --git a/src/core/src/main/java/org/apache/jmeter/gui/action/CompileJSR223TestElements.java b/src/core/src/main/java/org/apache/jmeter/gui/action/CompileJSR223TestElements.java index 7539d09d6d9..0c7b5995420 100644 --- a/src/core/src/main/java/org/apache/jmeter/gui/action/CompileJSR223TestElements.java +++ b/src/core/src/main/java/org/apache/jmeter/gui/action/CompileJSR223TestElements.java @@ -70,16 +70,16 @@ public void addNode(Object object, HashTree subTree) { JSR223TestElement element = (JSR223TestElement) userObject; TestBeanHelper.prepare(element); try { - log.info("Compiling {}", element.getName()); + log.info("Compiling JSR223 element named: '{}'", element.getName()); if(!element.compile()) { elementsWithCompilationErrors++; treeNode.setMarkedBySearch(true); } else { - log.info("Compilation succeeded for {}", element.getName()); + log.info("Compilation succeeded for JSR223 element named: '{}'", element.getName()); } } catch (Exception e) { treeNode.setMarkedBySearch(true); - log.error("Error compiling test element {}", element.getName(), e); + log.error("Error compiling JSR223 element named: '{}'", element.getName(), e); } } } diff --git a/src/core/src/main/java/org/apache/jmeter/util/JSR223TestElement.java b/src/core/src/main/java/org/apache/jmeter/util/JSR223TestElement.java index b46b555d152..19cc910da2c 100644 --- a/src/core/src/main/java/org/apache/jmeter/util/JSR223TestElement.java +++ b/src/core/src/main/java/org/apache/jmeter/util/JSR223TestElement.java @@ -114,7 +114,7 @@ protected ScriptEngine getScriptEngine() throws ScriptException { String lang = getScriptLanguageWithDefault(); ScriptEngine scriptEngine = getInstance().getEngineByName(lang); if (scriptEngine == null) { - throw new ScriptException("Cannot find engine named: '"+lang+"', ensure you set language field in JSR223 Test Element: "+getName()); + throw new ScriptException("Cannot find engine named: '"+lang+"', ensure you set language field in JSR223 element named: " + getName()); } return scriptEngine; @@ -192,11 +192,11 @@ protected Object processFileOrScript(ScriptEngine scriptEngine, final Bindings p if (!StringUtils.isEmpty(filename)) { if (!scriptFile.isFile()) { throw new ScriptException("Script file '" + scriptFile.getAbsolutePath() - + "' is not a file for element: " + getName()); + + "' is not a file for JSR223 element named: " + getName()); } if (!scriptFile.canRead()) { throw new ScriptException("Script file '" + scriptFile.getAbsolutePath() - + "' is not readable for element:" + getName()); + + "' is not readable for JSR223 element named: " + getName()); } if (!supportsCompilable) { try (BufferedReader fileReader = Files.newBufferedReader(scriptFile.toPath())) { @@ -232,7 +232,7 @@ protected Object processFileOrScript(ScriptEngine scriptEngine, final Bindings p return scriptEngine.eval(script, bindings); } } else { - throw new ScriptException("Both script file and script text are empty for element:" + getName()); + throw new ScriptException("Both script file and script text are empty for JSR223 element named: " + getName()); } } catch (ScriptException ex) { Throwable rootCause = ex.getCause(); @@ -257,11 +257,11 @@ private static CompiledScript getCompiledScript( } catch (ScriptCompilationInvocationTargetException e) { Throwable cause = e.getCause(); if (cause instanceof IOException) { - cause.addSuppressed(new IllegalStateException("Unable to compile script " + newCacheKey)); + cause.addSuppressed(new IllegalStateException("Unable to compile JSR223 script: " + newCacheKey)); throw (IOException) cause; } if (cause instanceof ScriptException) { - cause.addSuppressed(new IllegalStateException("Unable to compile script " + newCacheKey)); + cause.addSuppressed(new IllegalStateException("Unable to compile JSR223 script: " + newCacheKey)); throw (ScriptException) cause; } throw e; @@ -287,7 +287,7 @@ public boolean compile() ((Compilable) scriptEngine).compile(getScript()); return true; } catch (ScriptException e) { // NOSONAR - logger.error("Error compiling script for test element {}, error:{}", getName(), e.getMessage()); + logger.error("Error compiling script for JSR223 element named: '{}', error: {}", getName(), e.getMessage()); return false; } } else { @@ -297,7 +297,7 @@ public boolean compile() ((Compilable) scriptEngine).compile(fileReader); return true; } catch (ScriptException e) { // NOSONAR - logger.error("Error compiling script for test element {}, error:{}", getName(), e.getMessage()); + logger.error("Error compiling script for JSR223 element named: '{}', error: {}", getName(), e.getMessage()); return false; } } diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/Groovy.java b/src/functions/src/main/java/org/apache/jmeter/functions/Groovy.java index 23612d0eb23..aad80d35f42 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/Groovy.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/Groovy.java @@ -130,7 +130,7 @@ public synchronized String execute(SampleResult previousResult, Sampler currentS } } catch (Exception ex) // Mainly for bsh.EvalError { - log.warn("Error running groovy script", ex); + log.warn("Error running Groovy script in element named: {}", currentSampler.getName(), ex); } log.debug("__groovy({},{})={}",script, varName, resultStr); return resultStr; diff --git a/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/sampler/JSR223Sampler.java b/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/sampler/JSR223Sampler.java index 22ffd43dee1..b8a540c9599 100644 --- a/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/sampler/JSR223Sampler.java +++ b/src/protocol/java/src/main/java/org/apache/jmeter/protocol/java/sampler/JSR223Sampler.java @@ -74,7 +74,7 @@ public SampleResult sample(Entry entry) { result.setResponseData(ret.toString(), null); } } catch (IOException | ScriptException e) { - log.error("Problem in JSR223 script {}, message: {}", getName(), e, e); + log.error("Problem in JSR223 element named: '{}'", getName(), e); result.setSuccessful(false); result.setResponseCode("500"); // $NON-NLS-1$ result.setResponseMessage(e.toString()); From 52282d83f2f905c1c2a145d16fc566314a360db8 Mon Sep 17 00:00:00 2001 From: KingRabbid <13966341+KingRabbid@users.noreply.github.com> Date: Mon, 6 Oct 2025 23:42:45 +0300 Subject: [PATCH 3/6] Another set --- .../main/java/org/apache/jmeter/visualizers/BSFListener.java | 2 +- .../java/org/apache/jmeter/visualizers/BeanShellListener.java | 2 +- .../src/main/java/org/apache/jmeter/functions/Groovy.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/BSFListener.java b/src/components/src/main/java/org/apache/jmeter/visualizers/BSFListener.java index cbdbabc31d9..09bfb20d919 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/BSFListener.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/BSFListener.java @@ -52,7 +52,7 @@ public void sampleOccurred(SampleEvent event) { processFileOrScript(mgr); } catch (BSFException e) { if (log.isWarnEnabled()) { - log.warn("Problem in BSF script. {}", e.toString()); + log.warn("Problem in BSF element named: '{}'", getName(), e); } } finally { if (mgr != null) { diff --git a/src/components/src/main/java/org/apache/jmeter/visualizers/BeanShellListener.java b/src/components/src/main/java/org/apache/jmeter/visualizers/BeanShellListener.java index 8cbe234697e..920af76185c 100644 --- a/src/components/src/main/java/org/apache/jmeter/visualizers/BeanShellListener.java +++ b/src/components/src/main/java/org/apache/jmeter/visualizers/BeanShellListener.java @@ -66,7 +66,7 @@ public void sampleOccurred(SampleEvent se) { processFileOrScript(bshInterpreter); } catch (JMeterException e) { if (log.isWarnEnabled()) { - log.warn("Problem in BeanShell script. {}", e.toString()); + log.warn("Problem in BeanShell element named: '{}'", getName(), e); } } } diff --git a/src/functions/src/main/java/org/apache/jmeter/functions/Groovy.java b/src/functions/src/main/java/org/apache/jmeter/functions/Groovy.java index aad80d35f42..7b1087ced9f 100644 --- a/src/functions/src/main/java/org/apache/jmeter/functions/Groovy.java +++ b/src/functions/src/main/java/org/apache/jmeter/functions/Groovy.java @@ -130,7 +130,7 @@ public synchronized String execute(SampleResult previousResult, Sampler currentS } } catch (Exception ex) // Mainly for bsh.EvalError { - log.warn("Error running Groovy script in element named: {}", currentSampler.getName(), ex); + log.warn("Error running Groovy script in element named: '{}'", currentSampler.getName(), ex); } log.debug("__groovy({},{})={}",script, varName, resultStr); return resultStr; From c4579400f1a8fa3e8fdc27850b37253a77f45f76 Mon Sep 17 00:00:00 2001 From: KingRabbid <13966341+KingRabbid@users.noreply.github.com> Date: Fri, 10 Oct 2025 02:52:04 +0300 Subject: [PATCH 4/6] Missing brackets --- .../main/java/org/apache/jmeter/util/JSR223TestElement.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/src/main/java/org/apache/jmeter/util/JSR223TestElement.java b/src/core/src/main/java/org/apache/jmeter/util/JSR223TestElement.java index b44cb112438..91f9f1b1681 100644 --- a/src/core/src/main/java/org/apache/jmeter/util/JSR223TestElement.java +++ b/src/core/src/main/java/org/apache/jmeter/util/JSR223TestElement.java @@ -355,8 +355,9 @@ public void testEnded() { */ @Override public void testEnded(String host) { - if (COMPILED_SCRIPT_CACHE.estimatedSize() > 0) + if (COMPILED_SCRIPT_CACHE.estimatedSize() > 0) { logger.info("Compiled cache size: {}, stats: {}", COMPILED_SCRIPT_CACHE.estimatedSize(), COMPILED_SCRIPT_CACHE.stats()); + } COMPILED_SCRIPT_CACHE.invalidateAll(); scriptMd5 = null; } From 907fe105ebdb8ff1570366b63cd35067d8dd9365 Mon Sep 17 00:00:00 2001 From: KingRabbid <13966341+KingRabbid@users.noreply.github.com> Date: Thu, 23 Oct 2025 00:32:43 +0300 Subject: [PATCH 5/6] Bump Caffeine to 3.1.8. Add Caffeine Javadoc link. Add debug logging for cache missed. Consider compilation failure or unchecked cache compile as cach missed --- src/bom-thirdparty/build.gradle.kts | 2 +- .../apache/jmeter/util/JSR223TestElement.java | 138 +++++++++++++----- .../protocol/http/control/CacheManager.java | 2 +- xdocs/usermanual/properties_reference.xml | 7 +- 4 files changed, 105 insertions(+), 44 deletions(-) diff --git a/src/bom-thirdparty/build.gradle.kts b/src/bom-thirdparty/build.gradle.kts index 4d54c9168c7..19afa8a052d 100644 --- a/src/bom-thirdparty/build.gradle.kts +++ b/src/bom-thirdparty/build.gradle.kts @@ -47,7 +47,7 @@ dependencies { api("com.fasterxml.jackson.core:jackson-databind:2.16.1") api("com.fifesoft:rsyntaxtextarea:3.3.4") api("com.formdev:svgSalamander:1.1.4") - api("com.github.ben-manes.caffeine:caffeine:2.9.3") + api("com.github.ben-manes.caffeine:caffeine:3.1.8") api("com.github.weisj:darklaf-core:2.7.3") api("com.github.weisj:darklaf-extensions-rsyntaxarea:0.3.4") api("com.github.weisj:darklaf-property-loader:2.7.3") diff --git a/src/core/src/main/java/org/apache/jmeter/util/JSR223TestElement.java b/src/core/src/main/java/org/apache/jmeter/util/JSR223TestElement.java index 516321cc8b9..7952d4f6d07 100644 --- a/src/core/src/main/java/org/apache/jmeter/util/JSR223TestElement.java +++ b/src/core/src/main/java/org/apache/jmeter/util/JSR223TestElement.java @@ -46,12 +46,13 @@ import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.stats.CacheStats; /** * Base class for JSR223 Test elements */ public abstract class JSR223TestElement extends ScriptingTestElement - implements Serializable, TestStateListener + implements Serializable, TestStateListener { private static final long serialVersionUID = 233L; @@ -59,9 +60,12 @@ public abstract class JSR223TestElement extends ScriptingTestElement /** * Cache of compiled scripts */ - private static final Cache COMPILED_SCRIPT_CACHE = - Caffeine.from(JMeterUtils.getPropDefault("jsr223.compiled_scripts_cache_spec","maximumSize=" + - JMeterUtils.getPropDefault("jsr223.compiled_scripts_cache_size", 100) + ",recordStats")).build(); + private static Cache COMPILED_SCRIPT_CACHE; + + /** + * Used for locking cache initialization + */ + private static final Object lock = new Object(); /** * Lambdas can't throw checked exceptions, so we wrap cache loading failure with a runtime one. @@ -77,11 +81,11 @@ public synchronized Throwable fillInStackTrace() { } } - /** If not empty then script in ScriptText will be compiled and cached */ - private String cacheKey = ""; + /** If JSR223 element has checkbox 'Cache compile' checked then script in ScriptText will be compiled and cached */ + private String cacheChecked = ""; - /** md5 of the script, used as an unique key for the cache */ - private ScriptCacheKey scriptMd5; + /** Used as an unique key for the cache */ + private ScriptCacheKey scriptCacheKey; /** * Initialization On Demand Holder pattern @@ -97,7 +101,7 @@ private LazyHolder() { * @return ScriptEngineManager singleton */ public static ScriptEngineManager getInstance() { - return LazyHolder.INSTANCE; + return LazyHolder.INSTANCE; } protected JSR223TestElement() { @@ -201,13 +205,14 @@ protected Object processFileOrScript(ScriptEngine scriptEngine, final Bindings p return scriptEngine.eval(fileReader, bindings); } } - CompiledScript compiledScript; - ScriptCacheKey newCacheKey = - ScriptCacheKey.ofFile(getScriptLanguage(), scriptFile.getAbsolutePath(), scriptFile.lastModified()); - compiledScript = getCompiledScript(newCacheKey, key -> { + computeScriptCacheKey(scriptFile); + CompiledScript compiledScript = getCompiledScript(scriptCacheKey, key -> { try (BufferedReader fileReader = Files.newBufferedReader(scriptFile.toPath())) { return ((Compilable) scriptEngine).compile(fileReader); } catch (IOException | ScriptException e) { + if (logger.isDebugEnabled()) { + logger.warn("Cache missed access: for file script: '{}' for element named: '{}'", scriptFile.getAbsolutePath(), getName()); + } throw new ScriptCompilationInvocationTargetException(e); } }); @@ -215,17 +220,31 @@ protected Object processFileOrScript(ScriptEngine scriptEngine, final Bindings p } String script = getScript(); if (StringUtilities.isNotEmpty(script)) { - if (supportsCompilable && - !ScriptingBeanInfoSupport.FALSE_AS_STRING.equals(cacheKey)) { - computeScriptMD5(script); - CompiledScript compiledScript = getCompiledScript(scriptMd5, key -> { - try { - return ((Compilable) scriptEngine).compile(script); - } catch (ScriptException e) { - throw new ScriptCompilationInvocationTargetException(e); + if (supportsCompilable) { + if (!ScriptingBeanInfoSupport.FALSE_AS_STRING.equals(cacheChecked)) { + computeScriptCacheKey(script); + CompiledScript compiledScript = getCompiledScript(scriptCacheKey, key -> { + try { + return ((Compilable) scriptEngine).compile(script); + } catch (ScriptException e) { + if (logger.isDebugEnabled()) { + logger.debug("Cache missed access: failed compile of JSR223 element named: '{}'", getName()); + } + throw new ScriptCompilationInvocationTargetException(e); + } + }); + return compiledScript.eval(bindings); + } else { + computeScriptCacheKey(script.hashCode()); + //simulate a cache miss when JSR223 'Cache compiled script if available' is unchecked to have better view of cache usage + var unused = COMPILED_SCRIPT_CACHE.get(scriptCacheKey, k -> { + return null; + }); + if (logger.isDebugEnabled()) { + logger.debug("Cache missed access: 'Cache compile' is unchecked for JSR223 element named: '{}'", getName()); } - }); - return compiledScript.eval(bindings); + return scriptEngine.eval(script, bindings); + } } else { return scriptEngine.eval(script, bindings); } @@ -234,7 +253,7 @@ protected Object processFileOrScript(ScriptEngine scriptEngine, final Bindings p } } catch (ScriptException ex) { Throwable rootCause = ex.getCause(); - if(isStopCondition(rootCause)) { + if (isStopCondition(rootCause)) { throw (RuntimeException) ex.getCause(); } else { throw ex; @@ -272,7 +291,7 @@ private static CompiledScript getCompiledScript( * @throws ScriptException if compilation fails */ public boolean compile() - throws ScriptException, IOException { + throws ScriptException, IOException { String lang = getScriptLanguageWithDefault(); ScriptEngine scriptEngine = getInstance().getEngineByName(lang); boolean supportsCompilable = scriptEngine instanceof Compilable @@ -303,27 +322,46 @@ public boolean compile() } /** - * compute MD5 if it is null + * compute MD5 of a script if null */ - private void computeScriptMD5(String script) { + private void computeScriptCacheKey(String script) { // compute the md5 of the script if needed - if(scriptMd5 == null) { - scriptMd5 = ScriptCacheKey.ofString(DigestUtils.md5Hex(script)); + if (scriptCacheKey == null) { + scriptCacheKey = ScriptCacheKey.ofString(DigestUtils.md5Hex(script)); + } + } + + /** + * compute cache key for a file based script if null + */ + private void computeScriptCacheKey(File scriptFile) { + if (scriptCacheKey == null) { + scriptCacheKey = ScriptCacheKey.ofFile(getScriptLanguage(), scriptFile.getAbsolutePath(), scriptFile.lastModified()); } } /** - * @return the cacheKey + * compute cache key of a long value if null + */ + private void computeScriptCacheKey(int reference) { + if (scriptCacheKey == null) { + scriptCacheKey = ScriptCacheKey.ofString(Integer.toString(reference)); + } + } + + + /** + * @return the cacheChecked */ public String getCacheKey() { - return cacheKey; + return cacheChecked; } /** - * @param cacheKey the cacheKey to set + * @param cacheChecked the cacheChecked to set */ - public void setCacheKey(String cacheKey) { - this.cacheKey = cacheKey; + public void setCacheKey(String cacheChecked) { + this.cacheChecked = cacheChecked; } /** @@ -331,7 +369,7 @@ public void setCacheKey(String cacheKey) { */ @Override public void testStarted() { - // NOOP + testStarted(""); } /** @@ -339,7 +377,13 @@ public void testStarted() { */ @Override public void testStarted(String host) { - // NOOP + synchronized (lock) { + if (COMPILED_SCRIPT_CACHE == null) { + COMPILED_SCRIPT_CACHE = + Caffeine.from(JMeterUtils.getPropDefault("jsr223.compiled_scripts_cache_spec", "maximumSize=" + + JMeterUtils.getPropDefault("jsr223.compiled_scripts_cache_size", 100) + ",recordStats")).build(); + } + } } /** @@ -355,11 +399,25 @@ public void testEnded() { */ @Override public void testEnded(String host) { - if (COMPILED_SCRIPT_CACHE.estimatedSize() > 0) { - logger.info("Compiled cache size: {}, stats: {}", COMPILED_SCRIPT_CACHE.estimatedSize(), COMPILED_SCRIPT_CACHE.stats()); + synchronized (lock) { + if (COMPILED_SCRIPT_CACHE != null) { + CacheStats stats = COMPILED_SCRIPT_CACHE.stats(); + logger.info("JSR223 cached scripts: {}, requestsCount: {} (hitCount: {} + missedCount: {}), (hitRate: {}, missRate: {}), " + + "loadCount: {} (loadSuccessCount: {} + loadFailureCount: {}), " + + "evictionCount: {}, evictionWeight: {}, " + + "totalLoadTime: {} ms, averageLoadPenalty: {} ms", + COMPILED_SCRIPT_CACHE.estimatedSize(), + stats.requestCount(), stats.hitCount(), stats.missCount(), + String.format("%.02f", stats.hitRate()), String.format("%.02f", stats.missRate()), + stats.loadCount(), stats.loadSuccessCount(), stats.loadFailureCount(), + stats.evictionCount(), stats.evictionWeight(), + String.format("%.02f", (stats.totalLoadTime() / 100000f)), String.format("%.02f", (stats.averageLoadPenalty() / 100000f))); + COMPILED_SCRIPT_CACHE.invalidateAll(); + COMPILED_SCRIPT_CACHE.cleanUp(); + COMPILED_SCRIPT_CACHE = null; + } } - COMPILED_SCRIPT_CACHE.invalidateAll(); - scriptMd5 = null; + scriptCacheKey = null; } public String getScriptLanguage() { diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/control/CacheManager.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/control/CacheManager.java index 6b1c1d48b15..a09c2b3690a 100644 --- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/control/CacheManager.java +++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/control/CacheManager.java @@ -283,7 +283,7 @@ private void setCache(String lastModified, String cacheControl, String expires, } else { // Makes expiresDate effectively-final Date entryExpiresDate = expiresDate; - getCache().get( + var unused = getCache().get( url, key -> { CacheEntry cacheEntry = new CacheEntry(lastModified, entryExpiresDate, etag, null); diff --git a/xdocs/usermanual/properties_reference.xml b/xdocs/usermanual/properties_reference.xml index c1508fb19a9..663905f2dc3 100644 --- a/xdocs/usermanual/properties_reference.xml +++ b/xdocs/usermanual/properties_reference.xml @@ -1984,11 +1984,14 @@ JMETER-SERVER Used by JSR-223 elements.
Size of compiled scripts cache.
- Defaults to: 100
+ Defaults to: 100 + Used by JSR-223 elements.
Caffeine framework spec configuration in String format. Overrides jsr223.compiled_scripts_cache_size
- Defaults to: maximumSize=,recordStats
+ Defaults to: maximumSize=jsr223.compiled_scripts_cache_size,recordStats. + Extra details: Caffeine spec + From 7fc32f4525e3d8b2e218d2b38d79ba7f54924800 Mon Sep 17 00:00:00 2001 From: KingRabbid <13966341+KingRabbid@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:55:46 +0200 Subject: [PATCH 6/6] More granular JSR223 usage reporting --- bin/jmeter.properties | 2 + .../apache/jmeter/util/JSR223TestElement.java | 229 +++++++++++++++--- xdocs/usermanual/properties_reference.xml | 4 + 3 files changed, 196 insertions(+), 39 deletions(-) diff --git a/bin/jmeter.properties b/bin/jmeter.properties index 05235ccd8f6..df62a7a41e5 100644 --- a/bin/jmeter.properties +++ b/bin/jmeter.properties @@ -1238,6 +1238,8 @@ view.results.tree.renderers_order=.RenderAsText,.RenderAsRegexp,.RenderAsBoundar # Configurable options on the compiled scripts cache, overrides the jsr223.compiled_scripts_cache_size property # See com.github.benmanes.caffeine.cache.Caffeine API for details jsr223.compiled_scripts_cache_spec=maximumSize=100,recordStats +# How many entries to show in the JSR223 cache report (if enabled, i.e. > 0) +#jsr223.statsReportsTop=5 #--------------------------------------------------------------------------- # Classpath configuration diff --git a/src/core/src/main/java/org/apache/jmeter/util/JSR223TestElement.java b/src/core/src/main/java/org/apache/jmeter/util/JSR223TestElement.java index 7952d4f6d07..35d5d9ebc5f 100644 --- a/src/core/src/main/java/org/apache/jmeter/util/JSR223TestElement.java +++ b/src/core/src/main/java/org/apache/jmeter/util/JSR223TestElement.java @@ -22,7 +22,8 @@ import java.io.IOException; import java.io.Serializable; import java.nio.file.Files; -import java.util.Properties; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import javax.script.Bindings; @@ -40,7 +41,6 @@ import org.apache.jmeter.threads.JMeterContextService; import org.apache.jmeter.threads.JMeterVariables; import org.apache.jorphan.util.JOrphanUtils; -import org.apache.jorphan.util.StringUtilities; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -82,11 +82,26 @@ public synchronized Throwable fillInStackTrace() { } /** If JSR223 element has checkbox 'Cache compile' checked then script in ScriptText will be compiled and cached */ - private String cacheChecked = ""; + private String cacheKey = ""; /** Used as an unique key for the cache */ private ScriptCacheKey scriptCacheKey; + /** + * Holders for stats computation + */ + private static final Map computeScriptCacheKeyCounts = new ConcurrentHashMap<>(); + private static final Map computeScriptCacheKeyTimes = new ConcurrentHashMap<>(); + private static final Map getCompiledScriptCounts = new ConcurrentHashMap<>(); + private static final Map getCompiledScriptTimes = new ConcurrentHashMap<>(); + private static final Map fullRunTimes = new ConcurrentHashMap<>(); + private static final Map keys2Names = new ConcurrentHashMap<>(); + + /** + * Nanoseconds to milliseconds conversion factor + */ + private static final double ns2ms = 1_000_000.0; + /** * Initialization On Demand Holder pattern */ @@ -127,7 +142,7 @@ protected ScriptEngine getScriptEngine() throws ScriptException { */ private String getScriptLanguageWithDefault() { String lang = getScriptLanguage(); - if (StringUtilities.isNotEmpty(lang)) { + if (lang != null && !lang.isEmpty()) { return lang; } return DEFAULT_SCRIPT_LANGUAGE; @@ -185,13 +200,13 @@ protected Object processFileOrScript(ScriptEngine scriptEngine, final Bindings p } populateBindings(bindings); String filename = getFilename(); - File scriptFile = new File(filename); // Hack: bsh-2.0b5.jar BshScriptEngine implements Compilable but throws // "java.lang.Error: unimplemented" boolean supportsCompilable = scriptEngine instanceof Compilable && !"bsh.engine.BshScriptEngine".equals(scriptEngine.getClass().getName()); // NOSONAR // $NON-NLS-1$ try { - if (StringUtilities.isNotEmpty(filename)) { + if (filename != null && !filename.isEmpty()) { + File scriptFile = new File(filename); if (!scriptFile.isFile()) { throw new ScriptException("Script file '" + scriptFile.getAbsolutePath() + "' is not a file for JSR223 element named: " + getName()); @@ -211,39 +226,46 @@ protected Object processFileOrScript(ScriptEngine scriptEngine, final Bindings p return ((Compilable) scriptEngine).compile(fileReader); } catch (IOException | ScriptException e) { if (logger.isDebugEnabled()) { - logger.warn("Cache missed access: for file script: '{}' for element named: '{}'", scriptFile.getAbsolutePath(), getName()); + logger.debug("Cache missed access: for file script: '{}' for element named: '{}'", scriptFile.getAbsolutePath(), getName()); } throw new ScriptCompilationInvocationTargetException(e); } }); return compiledScript.eval(bindings); } + String script = getScript(); - if (StringUtilities.isNotEmpty(script)) { + if (script != null && !script.isEmpty()) { if (supportsCompilable) { - if (!ScriptingBeanInfoSupport.FALSE_AS_STRING.equals(cacheChecked)) { - computeScriptCacheKey(script); - CompiledScript compiledScript = getCompiledScript(scriptCacheKey, key -> { - try { - return ((Compilable) scriptEngine).compile(script); - } catch (ScriptException e) { - if (logger.isDebugEnabled()) { - logger.debug("Cache missed access: failed compile of JSR223 element named: '{}'", getName()); + long start = System.nanoTime(); + try { + if (!ScriptingBeanInfoSupport.FALSE_AS_STRING.equals(cacheKey)) { + computeScriptCacheKey(script); + CompiledScript compiledScript = getCompiledScript(scriptCacheKey, key -> { + try { + return ((Compilable) scriptEngine).compile(script); + } catch (ScriptException e) { + if (logger.isDebugEnabled()) { + logger.debug("Cache missed access: failed compile of JSR223 element named: '{}'", getName()); + } + throw new ScriptCompilationInvocationTargetException(e); } - throw new ScriptCompilationInvocationTargetException(e); + }); + return compiledScript.eval(bindings); + } else { + computeScriptCacheKey(script.hashCode()); + //simulate a cache miss when JSR223 'Cache compiled script if available' is unchecked to have better view of cache usage + COMPILED_SCRIPT_CACHE.get(scriptCacheKey, k -> { + return null; + }); + if (logger.isDebugEnabled()) { + logger.debug("Cache missed access: 'Cache compile' is unchecked for JSR223 element named: '{}'", getName()); } - }); - return compiledScript.eval(bindings); - } else { - computeScriptCacheKey(script.hashCode()); - //simulate a cache miss when JSR223 'Cache compiled script if available' is unchecked to have better view of cache usage - var unused = COMPILED_SCRIPT_CACHE.get(scriptCacheKey, k -> { - return null; - }); - if (logger.isDebugEnabled()) { - logger.debug("Cache missed access: 'Cache compile' is unchecked for JSR223 element named: '{}'", getName()); + return scriptEngine.eval(script, bindings); } - return scriptEngine.eval(script, bindings); + } finally { + Double duration = (double) ((System.nanoTime() - start) / ns2ms); //in ms + fullRunTimes.merge(scriptCacheKey, duration, Double::sum); } } else { return scriptEngine.eval(script, bindings); @@ -265,6 +287,7 @@ private static CompiledScript getCompiledScript( T newCacheKey, Function compiler ) throws IOException, ScriptException { + long start = System.nanoTime(); try { CompiledScript compiledScript = COMPILED_SCRIPT_CACHE.get(newCacheKey, compiler); if (compiledScript == null) { @@ -282,6 +305,10 @@ private static CompiledScript getCompiledScript( throw (ScriptException) cause; } throw e; + } finally { + Double duration = (double) ((System.nanoTime() - start) / ns2ms); //in ms + getCompiledScriptCounts.merge(newCacheKey, 1L, Long::sum); + getCompiledScriptTimes.merge(newCacheKey, duration, Double::sum); } } @@ -325,9 +352,17 @@ public boolean compile() * compute MD5 of a script if null */ private void computeScriptCacheKey(String script) { - // compute the md5 of the script if needed - if (scriptCacheKey == null) { - scriptCacheKey = ScriptCacheKey.ofString(DigestUtils.md5Hex(script)); + long start = System.nanoTime(); + try { + if (scriptCacheKey == null) { + // compute the md5 of the script if needed + scriptCacheKey = ScriptCacheKey.ofString(DigestUtils.md5Hex(script)); + keys2Names.put(scriptCacheKey, getName()); + } + } finally { + Double duration = (double) ((System.nanoTime() - start) / ns2ms); + computeScriptCacheKeyCounts.merge(scriptCacheKey, 1L, Long::sum); + computeScriptCacheKeyTimes.merge(scriptCacheKey, duration, Double::sum); } } @@ -335,8 +370,16 @@ private void computeScriptCacheKey(String script) { * compute cache key for a file based script if null */ private void computeScriptCacheKey(File scriptFile) { - if (scriptCacheKey == null) { - scriptCacheKey = ScriptCacheKey.ofFile(getScriptLanguage(), scriptFile.getAbsolutePath(), scriptFile.lastModified()); + long start = System.nanoTime(); + try { + if (scriptCacheKey == null) { + scriptCacheKey = ScriptCacheKey.ofFile(getScriptLanguage(), scriptFile.getAbsolutePath(), scriptFile.lastModified()); + keys2Names.put(scriptCacheKey, getName()); + } + } finally { + Double duration = (double) ((System.nanoTime() - start) / ns2ms); + computeScriptCacheKeyCounts.merge(scriptCacheKey, 1L, Long::sum); + computeScriptCacheKeyTimes.merge(scriptCacheKey, duration, Double::sum); } } @@ -344,8 +387,16 @@ private void computeScriptCacheKey(File scriptFile) { * compute cache key of a long value if null */ private void computeScriptCacheKey(int reference) { - if (scriptCacheKey == null) { - scriptCacheKey = ScriptCacheKey.ofString(Integer.toString(reference)); + long start = System.nanoTime(); + try { + if (scriptCacheKey == null) { + scriptCacheKey = ScriptCacheKey.ofString(Integer.toString(reference)); + keys2Names.put(scriptCacheKey, getName()); + } + } finally { + Double duration = (double) ((System.nanoTime() - start) / ns2ms); + computeScriptCacheKeyCounts.merge(scriptCacheKey, 1L, Long::sum); + computeScriptCacheKeyTimes.merge(scriptCacheKey, duration, Double::sum); } } @@ -354,14 +405,14 @@ private void computeScriptCacheKey(int reference) { * @return the cacheChecked */ public String getCacheKey() { - return cacheChecked; + return cacheKey; } /** * @param cacheChecked the cacheChecked to set */ public void setCacheKey(String cacheChecked) { - this.cacheChecked = cacheChecked; + this.cacheKey = cacheChecked; } /** @@ -402,7 +453,7 @@ public void testEnded(String host) { synchronized (lock) { if (COMPILED_SCRIPT_CACHE != null) { CacheStats stats = COMPILED_SCRIPT_CACHE.stats(); - logger.info("JSR223 cached scripts: {}, requestsCount: {} (hitCount: {} + missedCount: {}), (hitRate: {}, missRate: {}), " + + logger.info("JSR223 cache stats => scripts: {}, requestsCount: {} (hitCount: {} + missedCount: {}), (hitRate: {}, missRate: {}), " + "loadCount: {} (loadSuccessCount: {} + loadFailureCount: {}), " + "evictionCount: {}, evictionWeight: {}, " + "totalLoadTime: {} ms, averageLoadPenalty: {} ms", @@ -411,10 +462,24 @@ public void testEnded(String host) { String.format("%.02f", stats.hitRate()), String.format("%.02f", stats.missRate()), stats.loadCount(), stats.loadSuccessCount(), stats.loadFailureCount(), stats.evictionCount(), stats.evictionWeight(), - String.format("%.02f", (stats.totalLoadTime() / 100000f)), String.format("%.02f", (stats.averageLoadPenalty() / 100000f))); + String.format("%.02f", (stats.totalLoadTime() / ns2ms)), String.format("%.02f", (stats.averageLoadPenalty() / ns2ms))); COMPILED_SCRIPT_CACHE.invalidateAll(); COMPILED_SCRIPT_CACHE.cleanUp(); COMPILED_SCRIPT_CACHE = null; + + int topLimit = getTopContributorsLimit(); + if (topLimit > 0) { + logTopContributors(computeScriptCacheKeyCounts, computeScriptCacheKeyTimes, "computeScriptCacheKey"); + logTopContributors(getCompiledScriptCounts, getCompiledScriptTimes, "getCompiledScript"); + logTopContributors(computeScriptCacheKeyCounts, fullRunTimes, "processFileOrScript"); + } + + computeScriptCacheKeyCounts.clear(); + computeScriptCacheKeyTimes.clear(); + getCompiledScriptCounts.clear(); + getCompiledScriptTimes.clear(); + fullRunTimes.clear(); + keys2Names.clear(); } } scriptCacheKey = null; @@ -427,4 +492,90 @@ public String getScriptLanguage() { public void setScriptLanguage(String s) { scriptLanguage = s; } + + /** + * Read configured maximum number of top contributors to report. + * Property: jsr223.statsReportsTop (default "5"). + */ + private static int getTopContributorsLimit() { + String raw = JMeterUtils.getPropDefault("jsr223.statsReportsTop", "5"); + try { + int v = Integer.parseInt(raw.trim()); + return v < 0 ? 0 : v; + } catch (Exception ex) { + return 5; + } + } + + private static void logTopContributors(Map counts, Map durations, String method) { + // Build entries with key, name, count, total and average durations + int configuredLimit = getTopContributorsLimit(); + if (configuredLimit == 0) { + return; + } + + class Entry { + final ScriptCacheKey key; + final String name; + final long count; + final double totalDuration; + final double avgDuration; + + Entry(ScriptCacheKey key, String name, long count, double totalDuration) { + this.key = key; + this.name = name == null ? key.toString() : name; + this.count = count; + this.totalDuration = totalDuration; + this.avgDuration = count == 0 ? 0.0 : (totalDuration / count); + } + } + + List list = new ArrayList<>(); + for (Map.Entry e : counts.entrySet()) { + ScriptCacheKey key = e.getKey(); + long count = e.getValue() == null ? 0L : e.getValue(); + double total = durations.getOrDefault(key, 0.0); + list.add(new Entry(key, keys2Names.get(key), count, total)); + } + + if (list.isEmpty()) { + logger.info("{}: No contributors recorded.", method); + return; + } + int limit = Math.min(configuredLimit, list.size()); + + StringBuilder sb = new StringBuilder(); + sb.append(String.format("Heaviest top %d contributors by their total '%s' execution times (ms)%n", limit, method)); + + for (int j = 0; j < 2; j++) { + if (j == 0) { + list.sort((a, b) -> Double.compare(b.totalDuration, a.totalDuration)); + sb.append(String.format("%n===> By total duration (ms) <===%n")); + } else { + list.sort((a, b) -> Double.compare(b.avgDuration, a.avgDuration)); + sb.append(String.format("%n===> By average duration (ms) <===%n")); + } + sb.append(String.format(Locale.ROOT, "%-5s %-60s %12s %15s %12s%n", "Rank", "Element name", "Calls", "Duration(ms)", "Avg(ms)")); + + long sumCounts = 0L; + double sumTotal = 0.0; + for (int i = 0; i < limit; i++) { + Entry ent = list.get(i); + String totalStr = String.format(Locale.ROOT, "%.3f", ent.totalDuration); + String avgStr = String.format(Locale.ROOT, "%.3f", ent.avgDuration); + String name = ent.name == null ? ent.key.toString() : ent.name; + String displayName = name.length() > 60 ? name.substring(0, 57) + "..." : name; + sb.append(String.format(Locale.ROOT, "%-5d %-60s %12d %15s %12s%n", (i + 1), displayName, ent.count, totalStr, avgStr)); + sumCounts += ent.count; + sumTotal += ent.totalDuration; + } + + // SUM row for the printed items: show summed count and total duration and averaged avg + String sumTotalStr = String.format(Locale.ROOT, "%.3f", sumTotal); + String sumAvgStr = String.format(Locale.ROOT, "%.3f", (sumCounts == 0L ? 0.0 : (sumTotal / sumCounts))); + sb.append(String.format(Locale.ROOT, "%-5s %-60s %12d %15s %12s%n", "", "Total:", sumCounts, sumTotalStr, sumAvgStr)); + } + logger.info(sb.toString()); + } + } diff --git a/xdocs/usermanual/properties_reference.xml b/xdocs/usermanual/properties_reference.xml index 663905f2dc3..bb5b0603323 100644 --- a/xdocs/usermanual/properties_reference.xml +++ b/xdocs/usermanual/properties_reference.xml @@ -1992,6 +1992,10 @@ JMETER-SERVER Defaults to: maximumSize=jsr223.compiled_scripts_cache_size,recordStats. Extra details: Caffeine spec + + Used by JSR-223 elements.
+ Should usage reports be printed at the end of the run (value > 0) then how many items to display.
+ Defaults to: statsReportsTop=5