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/5] 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/5] 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/5] 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/5] 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/5] 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 +