From e04c2b673c3690116ba3af3dde45c0860f4f277a Mon Sep 17 00:00:00 2001 From: Johnny Hujol Date: Thu, 11 Sep 2025 13:09:38 +0200 Subject: [PATCH 01/14] ref: improve logging when validation is false Signed-off-by: Johnny Hujol --- .../ElasticsearchBackendClient.java | 57 ++++++++++++------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java b/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java index c458a35..931b11d 100644 --- a/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java +++ b/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java @@ -228,32 +228,43 @@ private void setSSLConfiguration(BackendListenerContext context) { @Override public void handleSampleResults(List results, BackendListenerContext context) { - for (SampleResult sr : results) { - ElasticSearchMetric metric = new ElasticSearchMetric(sr, context.getParameter(ES_TEST_MODE), - context.getParameter(ES_TIMESTAMP), this.buildNumber, - context.getBooleanParameter(ES_PARSE_REQ_HEADERS, false), - context.getBooleanParameter(ES_PARSE_RES_HEADERS, false), fields); + if (results.isEmpty()) { + logger.warn("There are no sampler results to handle!"); + return; + } - if (validateSample(context, sr)) { - try { - this.sender.addToList(new Gson().toJson(metric.getMetric(context))); - } catch (Exception e) { - logger.error( - "The ElasticSearch Backend Listener was unable to add sampler to the list of samplers to send... More info in JMeter's console."); - e.printStackTrace(); - } - } + for (SampleResult sr : results) { + logger.info(String.format("Handle sampler result %s: %s", sr.getSampleLabel(), sr.getResponseDataAsString())); + ElasticSearchMetric metric = new ElasticSearchMetric(sr, context.getParameter(ES_TEST_MODE), + context.getParameter(ES_TIMESTAMP), this.buildNumber, + context.getBooleanParameter(ES_PARSE_REQ_HEADERS, false), + context.getBooleanParameter(ES_PARSE_RES_HEADERS, false), fields); + + if (validateSample(context, sr)) { + try { + this.sender.addToList(new Gson().toJson(metric.getMetric(context))); + } + catch (Exception e) { + logger.error( + "The ElasticSearch Backend Listener was unable to add sampler to the list of samplers to send... More info in JMeter's console."); + e.printStackTrace(); + } + } else { + logger.warn(String.format("Not valid sampler result %s: %s", sr.getSampleLabel(), sr.getResponseDataAsString())); } + } - if (this.sender.getListSize() >= this.bulkSize) { - try { - this.sender.sendRequest(this.esVersion); - } catch (Exception e) { - logger.error("Error occured while sending bulk request.", e); - } finally { - this.sender.clearList(); - } + if (this.sender.getListSize() >= this.bulkSize) { + try { + this.sender.sendRequest(this.esVersion); + } + catch (Exception e) { + logger.error("Error occurred while sending bulk request.", e); + } + finally { + this.sender.clearList(); } + } } @Override @@ -305,6 +316,8 @@ private boolean validateSample(BackendListenerContext context, SampleResult sr) valid = true; break; } else { + logger.warn(String.format("Invalidate sample label %s, it may start with '!!' or it may not contain filter %s. " + + "Check the %s property value in the JMX file. Leave it empty to increase validation", sampleLabel, filter, ES_SAMPLE_FILTER)); valid = false; } } From 4522c4787f4bb269cd3e069a9e41b2ac24f59fff Mon Sep 17 00:00:00 2001 From: Johnny Hujol Date: Tue, 7 Oct 2025 15:28:59 +0200 Subject: [PATCH 02/14] feat(logging): simplify sampler result logging in handleSampleResults - Trim response string once and use parameterized logging for sampler results. - Improves readability and performance by avoiding redundant substring and string formatting. --- .../ElasticsearchBackendClient.java | 563 +++++++++--------- 1 file changed, 287 insertions(+), 276 deletions(-) diff --git a/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java b/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java index 931b11d..cfbcf28 100644 --- a/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java +++ b/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java @@ -1,9 +1,10 @@ - package io.github.delirius325.jmeter.backendlistener.elasticsearch; - -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +package io.github.delirius325.jmeter.backendlistener.elasticsearch; +import com.amazonaws.auth.AWS4Signer; +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; +import com.amazonaws.http.AWSRequestSigningApacheInterceptor; +import com.google.gson.Gson; import org.apache.commons.io.FilenameUtils; import org.apache.http.HttpHost; import org.apache.http.HttpRequestInterceptor; @@ -20,314 +21,324 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.amazonaws.auth.AWS4Signer; -import com.amazonaws.auth.AWSCredentialsProvider; -import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; -import com.amazonaws.http.AWSRequestSigningApacheInterceptor; -import com.google.gson.Gson; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class ElasticsearchBackendClient extends AbstractBackendListenerClient { - private static final String BUILD_NUMBER = "BuildNumber"; - private static final String ES_SCHEME = "es.scheme"; - private static final String ES_HOST = "es.host"; - private static final String ES_PORT = "es.port"; - private static final String ES_INDEX = "es.index"; - private static final String ES_FIELDS = "es.fields"; - private static final String ES_TIMESTAMP = "es.timestamp"; - private static final String ES_BULK_SIZE = "es.bulk.size"; - private static final String ES_TIMEOUT_MS = "es.timout.ms"; - private static final String ES_SAMPLE_FILTER = "es.sample.filter"; - private static final String ES_TEST_MODE = "es.test.mode"; - private static final String ES_AUTH_USER = "es.xpack.user"; - private static final String ES_AUTH_PWD = "es.xpack.password"; - private static final String ES_PARSE_REQ_HEADERS = "es.parse.all.req.headers"; - private static final String ES_PARSE_RES_HEADERS = "es.parse.all.res.headers"; - private static final String ES_AWS_ENDPOINT = "es.aws.endpoint"; - private static final String ES_AWS_REGION = "es.aws.region"; - private static final String ES_SSL_TRUSTSTORE_PATH = "es.ssl.truststore.path"; - private static final String ES_SSL_TRUSTSTORE_PW = "es.ssl.truststore.pw"; - private static final String ES_SSL_KEYSTORE_PATH = "es.ssl.keystore.path"; - private static final String ES_SSL_KEYSTORE_PW = "es.ssl.keystore.pw"; - private static final String ES_SSL_VERIFICATION_MODE = "es.ssl.verificationMode"; - private static final long DEFAULT_TIMEOUT_MS = 200L; - private static final String SERVICE_NAME = "es"; - private static RestClient client; - private static final Logger logger = LoggerFactory.getLogger(ElasticsearchBackendClient.class); - private static final AWSCredentialsProvider credentialsProvider = new DefaultAWSCredentialsProviderChain(); - private static final Map DEFAULT_ARGS = new LinkedHashMap<>(); - static { - DEFAULT_ARGS.put(ES_SCHEME, "http"); - DEFAULT_ARGS.put(ES_HOST, null); - DEFAULT_ARGS.put(ES_PORT, "9200"); - DEFAULT_ARGS.put(ES_INDEX, null); - DEFAULT_ARGS.put(ES_TIMESTAMP, "yyyy-MM-dd'T'HH:mm:ss.SSSZZ"); - DEFAULT_ARGS.put(ES_BULK_SIZE, "100"); - DEFAULT_ARGS.put(ES_TIMEOUT_MS, Long.toString(DEFAULT_TIMEOUT_MS)); - DEFAULT_ARGS.put(ES_SAMPLE_FILTER, null); - DEFAULT_ARGS.put(ES_FIELDS, null); - DEFAULT_ARGS.put(ES_TEST_MODE, "info"); - DEFAULT_ARGS.put(ES_AUTH_USER, ""); - DEFAULT_ARGS.put(ES_AUTH_PWD, ""); - DEFAULT_ARGS.put(ES_PARSE_REQ_HEADERS, "false"); - DEFAULT_ARGS.put(ES_PARSE_RES_HEADERS, "false"); - DEFAULT_ARGS.put(ES_AWS_ENDPOINT, ""); - DEFAULT_ARGS.put(ES_AWS_REGION, ""); - DEFAULT_ARGS.put(ES_SSL_TRUSTSTORE_PATH, ""); - DEFAULT_ARGS.put(ES_SSL_TRUSTSTORE_PW, ""); - DEFAULT_ARGS.put(ES_SSL_KEYSTORE_PATH, ""); - DEFAULT_ARGS.put(ES_SSL_KEYSTORE_PW, ""); - DEFAULT_ARGS.put(ES_SSL_VERIFICATION_MODE, "full"); - } - private ElasticSearchMetricSender sender; - private Set modes; - private Set filters; - private Set fields; - private int buildNumber; - private int bulkSize; - private int esVersion; - private long timeoutMs; + private static final String BUILD_NUMBER = "BuildNumber"; + private static final String ES_SCHEME = "es.scheme"; + private static final String ES_HOST = "es.host"; + private static final String ES_PORT = "es.port"; + private static final String ES_INDEX = "es.index"; + private static final String ES_FIELDS = "es.fields"; + private static final String ES_TIMESTAMP = "es.timestamp"; + private static final String ES_BULK_SIZE = "es.bulk.size"; + private static final String ES_TIMEOUT_MS = "es.timout.ms"; + private static final String ES_SAMPLE_FILTER = "es.sample.filter"; + private static final String ES_TEST_MODE = "es.test.mode"; + private static final String ES_AUTH_USER = "es.xpack.user"; + private static final String ES_AUTH_PWD = "es.xpack.password"; + private static final String ES_PARSE_REQ_HEADERS = "es.parse.all.req.headers"; + private static final String ES_PARSE_RES_HEADERS = "es.parse.all.res.headers"; + private static final String ES_AWS_ENDPOINT = "es.aws.endpoint"; + private static final String ES_AWS_REGION = "es.aws.region"; + private static final String ES_SSL_TRUSTSTORE_PATH = "es.ssl.truststore.path"; + private static final String ES_SSL_TRUSTSTORE_PW = "es.ssl.truststore.pw"; + private static final String ES_SSL_KEYSTORE_PATH = "es.ssl.keystore.path"; + private static final String ES_SSL_KEYSTORE_PW = "es.ssl.keystore.pw"; + private static final String ES_SSL_VERIFICATION_MODE = "es.ssl.verificationMode"; + private static final long DEFAULT_TIMEOUT_MS = 200L; + private static final String SERVICE_NAME = "es"; + private static RestClient client; + private static final Logger logger = LoggerFactory.getLogger(ElasticsearchBackendClient.class); + private static final AWSCredentialsProvider credentialsProvider = new DefaultAWSCredentialsProviderChain(); + private static final Map DEFAULT_ARGS = new LinkedHashMap<>(); - private static final TrustStrategy TRUST_ALL_STRATEGY = (chain, authType) -> true; + static { + DEFAULT_ARGS.put(ES_SCHEME, "http"); + DEFAULT_ARGS.put(ES_HOST, null); + DEFAULT_ARGS.put(ES_PORT, "9200"); + DEFAULT_ARGS.put(ES_INDEX, null); + DEFAULT_ARGS.put(ES_TIMESTAMP, "yyyy-MM-dd'T'HH:mm:ss.SSSZZ"); + DEFAULT_ARGS.put(ES_BULK_SIZE, "100"); + DEFAULT_ARGS.put(ES_TIMEOUT_MS, Long.toString(DEFAULT_TIMEOUT_MS)); + DEFAULT_ARGS.put(ES_SAMPLE_FILTER, null); + DEFAULT_ARGS.put(ES_FIELDS, null); + DEFAULT_ARGS.put(ES_TEST_MODE, "info"); + DEFAULT_ARGS.put(ES_AUTH_USER, ""); + DEFAULT_ARGS.put(ES_AUTH_PWD, ""); + DEFAULT_ARGS.put(ES_PARSE_REQ_HEADERS, "false"); + DEFAULT_ARGS.put(ES_PARSE_RES_HEADERS, "false"); + DEFAULT_ARGS.put(ES_AWS_ENDPOINT, ""); + DEFAULT_ARGS.put(ES_AWS_REGION, ""); + DEFAULT_ARGS.put(ES_SSL_TRUSTSTORE_PATH, ""); + DEFAULT_ARGS.put(ES_SSL_TRUSTSTORE_PW, ""); + DEFAULT_ARGS.put(ES_SSL_KEYSTORE_PATH, ""); + DEFAULT_ARGS.put(ES_SSL_KEYSTORE_PW, ""); + DEFAULT_ARGS.put(ES_SSL_VERIFICATION_MODE, "full"); + } - @Override - public Arguments getDefaultParameters() { - Arguments arguments = new Arguments(); - DEFAULT_ARGS.forEach(arguments::addArgument); - return arguments; - } + private ElasticSearchMetricSender sender; + private Set modes; + private Set filters; + private Set fields; + private int buildNumber; + private int bulkSize; + private int esVersion; + private long timeoutMs; - @Override - public void setupTest(BackendListenerContext context) throws Exception { - try { - this.filters = new HashSet<>(); - this.fields = new HashSet<>(); - this.modes = new HashSet<>(Arrays.asList("info", "debug", "error", "quiet")); - this.bulkSize = Integer.parseInt(context.getParameter(ES_BULK_SIZE)); - this.timeoutMs = Integer.parseInt((context.getParameter(ES_TIMEOUT_MS))); - this.buildNumber = (JMeterUtils.getProperty(ElasticsearchBackendClient.BUILD_NUMBER) != null - && !JMeterUtils.getProperty(ElasticsearchBackendClient.BUILD_NUMBER).trim().equals("")) - ? Integer.parseInt(JMeterUtils.getProperty(ElasticsearchBackendClient.BUILD_NUMBER)) : 0; + private static final TrustStrategy TRUST_ALL_STRATEGY = (chain, authType) -> true; - setSSLConfiguration(context); + @Override + public Arguments getDefaultParameters() { + Arguments arguments = new Arguments(); + DEFAULT_ARGS.forEach(arguments::addArgument); + return arguments; + } - if (context.getParameter(ES_AWS_ENDPOINT).equalsIgnoreCase("")) { - client = RestClient - .builder(new HttpHost(context.getParameter(ES_HOST), - Integer.parseInt(context.getParameter(ES_PORT)), context.getParameter(ES_SCHEME))) - .setHttpClientConfigCallback(httpAsyncClientBuilder -> { - if (context.getParameter(ES_SSL_VERIFICATION_MODE).equalsIgnoreCase("none")) { - logger.info("Will trust all remote SSL certificates."); - final SSLContextBuilder contextBuilder = new SSLContextBuilder(); - try { - contextBuilder.loadTrustMaterial(TRUST_ALL_STRATEGY); - httpAsyncClientBuilder.setSSLContext(contextBuilder.build()); - httpAsyncClientBuilder.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE); - } - catch (Exception e) { - // NOTE: purposedly ignored as this strategy does not use any custom algorithm - // or certificate - } - } - return httpAsyncClientBuilder; - }) - .setRequestConfigCallback(requestConfigBuilder -> requestConfigBuilder.setConnectTimeout(5000) - .setSocketTimeout((int) timeoutMs)) - .setFailureListener(new RestClient.FailureListener() { - @Override - public void onFailure(Node node) { - logger.error("Error with node: " + node.toString()); - } - }).build(); - } else { - AWS4Signer signer = new AWS4Signer(); - signer.setServiceName(SERVICE_NAME); - signer.setRegionName(context.getParameter(ES_AWS_REGION)); - HttpRequestInterceptor interceptor = new AWSRequestSigningApacheInterceptor(SERVICE_NAME, signer, - credentialsProvider); - client = RestClient.builder(HttpHost.create(context.getParameter(ES_AWS_ENDPOINT))) - .setHttpClientConfigCallback(hacb -> hacb.addInterceptorLast(interceptor)).build(); + @Override + public void setupTest(BackendListenerContext context) throws Exception { + try { + this.filters = new HashSet<>(); + this.fields = new HashSet<>(); + this.modes = new HashSet<>(Arrays.asList("info", "debug", "error", "quiet")); + this.bulkSize = Integer.parseInt(context.getParameter(ES_BULK_SIZE)); + this.timeoutMs = Integer.parseInt((context.getParameter(ES_TIMEOUT_MS))); + this.buildNumber = (JMeterUtils.getProperty(ElasticsearchBackendClient.BUILD_NUMBER) != null + && !JMeterUtils.getProperty(ElasticsearchBackendClient.BUILD_NUMBER).trim().equals("")) + ? Integer.parseInt(JMeterUtils.getProperty(ElasticsearchBackendClient.BUILD_NUMBER)) : 0; + + setSSLConfiguration(context); + + if (context.getParameter(ES_AWS_ENDPOINT).equalsIgnoreCase("")) { + client = RestClient + .builder(new HttpHost(context.getParameter(ES_HOST), + Integer.parseInt(context.getParameter(ES_PORT)), context.getParameter(ES_SCHEME))) + .setHttpClientConfigCallback(httpAsyncClientBuilder -> { + if (context.getParameter(ES_SSL_VERIFICATION_MODE).equalsIgnoreCase("none")) { + logger.info("Will trust all remote SSL certificates."); + final SSLContextBuilder contextBuilder = new SSLContextBuilder(); + try { + contextBuilder.loadTrustMaterial(TRUST_ALL_STRATEGY); + httpAsyncClientBuilder.setSSLContext(contextBuilder.build()); + httpAsyncClientBuilder.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE); + } + catch (Exception e) { + // NOTE: purposedly ignored as this strategy does not use any custom algorithm + // or certificate + } + } + return httpAsyncClientBuilder; + }) + .setRequestConfigCallback(requestConfigBuilder -> requestConfigBuilder.setConnectTimeout(5000) + .setSocketTimeout((int) timeoutMs)) + .setFailureListener(new RestClient.FailureListener() { + @Override + public void onFailure(Node node) { + logger.error("Error with node: " + node.toString()); } + }).build(); + } + else { + AWS4Signer signer = new AWS4Signer(); + signer.setServiceName(SERVICE_NAME); + signer.setRegionName(context.getParameter(ES_AWS_REGION)); + HttpRequestInterceptor interceptor = new AWSRequestSigningApacheInterceptor(SERVICE_NAME, signer, + credentialsProvider); + client = RestClient.builder(HttpHost.create(context.getParameter(ES_AWS_ENDPOINT))) + .setHttpClientConfigCallback(hacb -> hacb.addInterceptorLast(interceptor)).build(); + } - convertParameterToSet(context, ES_SAMPLE_FILTER, this.filters); - convertParameterToSet(context, ES_FIELDS, this.fields); + convertParameterToSet(context, ES_SAMPLE_FILTER, this.filters); + convertParameterToSet(context, ES_FIELDS, this.fields); - this.sender = new ElasticSearchMetricSender(client, context.getParameter(ES_INDEX).toLowerCase(), - context.getParameter(ES_AUTH_USER), context.getParameter(ES_AUTH_PWD), - context.getParameter(ES_AWS_ENDPOINT)); - this.sender.createIndex(); - this.esVersion = sender.getElasticSearchVersion(); + this.sender = new ElasticSearchMetricSender(client, context.getParameter(ES_INDEX).toLowerCase(), + context.getParameter(ES_AUTH_USER), context.getParameter(ES_AUTH_PWD), + context.getParameter(ES_AWS_ENDPOINT)); + this.sender.createIndex(); + this.esVersion = sender.getElasticSearchVersion(); - checkTestMode(context.getParameter(ES_TEST_MODE)); - super.setupTest(context); - } catch (Exception e) { - throw new IllegalStateException("Unable to connect to the ElasticSearch engine", e); - } + checkTestMode(context.getParameter(ES_TEST_MODE)); + super.setupTest(context); } - - /** - * Method that converts a semicolon separated list contained in a parameter into a string set - * @param context - * @param parameter - * @param set - */ - private void convertParameterToSet(BackendListenerContext context, String parameter, Set set) { - String[] array = (context.getParameter(parameter).contains(";")) ? context.getParameter(parameter).split(";") - : new String[] { context.getParameter(parameter) }; - if (array.length > 0 && !array[0].trim().equals("")) { - for (String entry : array) { - set.add(entry.toLowerCase().trim()); - if(logger.isDebugEnabled()) - logger.debug("Parsed from " + parameter + ": " + entry.toLowerCase().trim()); - } - } + catch (Exception e) { + throw new IllegalStateException("Unable to connect to the ElasticSearch engine", e); } + } - /** - * Method that sets the SSL configuration to be able to send requests to a secured endpoint - * @param context - */ - private void setSSLConfiguration(BackendListenerContext context) { - String keyStorePath = context.getParameter(ES_SSL_KEYSTORE_PATH); - if (!keyStorePath.equalsIgnoreCase("")) { - logger.warn("KeyStore system properties overwritten by ES SSL configuration."); - System.setProperty("javax.net.ssl.keyStore", keyStorePath); - System.setProperty("javax.net.ssl.keyStorePassword", context.getParameter(ES_SSL_KEYSTORE_PW)); - switch (FilenameUtils.getExtension(keyStorePath)) { - case "jks": - System.setProperty("javax.net.ssl.keyStoreType", "jks"); - break; - case "p12": - System.setProperty("javax.net.ssl.keyStoreType", "pkcs12"); - break; - default: - System.setProperty("javax.net.ssl.keyStoreType", ""); - break; - } - } - - String trustStorePath = context.getParameter(ES_SSL_TRUSTSTORE_PATH); - if (!trustStorePath.equalsIgnoreCase("")) { - logger.warn("TrustStore system properties overwritten by ES SSL configuration."); - System.setProperty("javax.net.ssl.trustStore", trustStorePath); - System.setProperty("javax.net.ssl.trustStorePassword", context.getParameter(ES_SSL_TRUSTSTORE_PW)); - switch (FilenameUtils.getExtension(trustStorePath)) { - case "jks": - System.setProperty("javax.net.ssl.trustStoreType", "jks"); - break; - case "p12": - System.setProperty("javax.net.ssl.trustStoreType", "pkcs12"); - break; - default: - System.setProperty("javax.net.ssl.trustStoreType", ""); - break; - } - } + /** + * Method that converts a semicolon separated list contained in a parameter into a string set + * + * @param context + * @param parameter + * @param set + */ + private void convertParameterToSet(BackendListenerContext context, String parameter, Set set) { + String[] array = (context.getParameter(parameter).contains(";")) ? context.getParameter(parameter).split(";") + : new String[]{context.getParameter(parameter)}; + if (array.length > 0 && !array[0].trim().equals("")) { + for (String entry : array) { + set.add(entry.toLowerCase().trim()); + if (logger.isDebugEnabled()) + logger.debug("Parsed from " + parameter + ": " + entry.toLowerCase().trim()); + } + } + } + /** + * Method that sets the SSL configuration to be able to send requests to a secured endpoint + * + * @param context + */ + private void setSSLConfiguration(BackendListenerContext context) { + String keyStorePath = context.getParameter(ES_SSL_KEYSTORE_PATH); + if (!keyStorePath.equalsIgnoreCase("")) { + logger.warn("KeyStore system properties overwritten by ES SSL configuration."); + System.setProperty("javax.net.ssl.keyStore", keyStorePath); + System.setProperty("javax.net.ssl.keyStorePassword", context.getParameter(ES_SSL_KEYSTORE_PW)); + switch (FilenameUtils.getExtension(keyStorePath)) { + case "jks": + System.setProperty("javax.net.ssl.keyStoreType", "jks"); + break; + case "p12": + System.setProperty("javax.net.ssl.keyStoreType", "pkcs12"); + break; + default: + System.setProperty("javax.net.ssl.keyStoreType", ""); + break; + } } - @Override - public void handleSampleResults(List results, BackendListenerContext context) { - if (results.isEmpty()) { - logger.warn("There are no sampler results to handle!"); - return; + String trustStorePath = context.getParameter(ES_SSL_TRUSTSTORE_PATH); + if (!trustStorePath.equalsIgnoreCase("")) { + logger.warn("TrustStore system properties overwritten by ES SSL configuration."); + System.setProperty("javax.net.ssl.trustStore", trustStorePath); + System.setProperty("javax.net.ssl.trustStorePassword", context.getParameter(ES_SSL_TRUSTSTORE_PW)); + switch (FilenameUtils.getExtension(trustStorePath)) { + case "jks": + System.setProperty("javax.net.ssl.trustStoreType", "jks"); + break; + case "p12": + System.setProperty("javax.net.ssl.trustStoreType", "pkcs12"); + break; + default: + System.setProperty("javax.net.ssl.trustStoreType", ""); + break; } + } - for (SampleResult sr : results) { - logger.info(String.format("Handle sampler result %s: %s", sr.getSampleLabel(), sr.getResponseDataAsString())); - ElasticSearchMetric metric = new ElasticSearchMetric(sr, context.getParameter(ES_TEST_MODE), - context.getParameter(ES_TIMESTAMP), this.buildNumber, - context.getBooleanParameter(ES_PARSE_REQ_HEADERS, false), - context.getBooleanParameter(ES_PARSE_RES_HEADERS, false), fields); + } - if (validateSample(context, sr)) { - try { - this.sender.addToList(new Gson().toJson(metric.getMetric(context))); - } - catch (Exception e) { - logger.error( - "The ElasticSearch Backend Listener was unable to add sampler to the list of samplers to send... More info in JMeter's console."); - e.printStackTrace(); - } - } else { - logger.warn(String.format("Not valid sampler result %s: %s", sr.getSampleLabel(), sr.getResponseDataAsString())); - } - } + @Override + public void handleSampleResults(List results, BackendListenerContext context) { + if (results.isEmpty()) { + logger.warn("There are no sampler results to handle!"); + return; + } + + for (SampleResult sr : results) { + String respString = sr.getResponseDataAsString(); + respString = respString.substring(0, Math.min(100, respString.length())); + logger.info("Handle sampler result {}: {}", sr.getSampleLabel(), respString); + ElasticSearchMetric metric = new ElasticSearchMetric(sr, context.getParameter(ES_TEST_MODE), + context.getParameter(ES_TIMESTAMP), this.buildNumber, + context.getBooleanParameter(ES_PARSE_REQ_HEADERS, false), + context.getBooleanParameter(ES_PARSE_RES_HEADERS, false), fields); - if (this.sender.getListSize() >= this.bulkSize) { + if (validateSample(context, sr)) { try { - this.sender.sendRequest(this.esVersion); + this.sender.addToList(new Gson().toJson(metric.getMetric(context))); } catch (Exception e) { - logger.error("Error occurred while sending bulk request.", e); - } - finally { - this.sender.clearList(); + logger.error( + "The ElasticSearch Backend Listener was unable to add sampler to the list of samplers to send... More info in JMeter's console."); + e.printStackTrace(); } } + else { + logger.warn(String.format("Not valid sampler result %s: %s", sr.getSampleLabel(), respString)); + } } - @Override - public void teardownTest(BackendListenerContext context) throws Exception { - if (this.sender.getListSize() > 0) { - this.sender.sendRequest(this.esVersion); - } - this.sender.closeConnection(); - super.teardownTest(context); + if (this.sender.getListSize() >= this.bulkSize) { + try { + this.sender.sendRequest(this.esVersion); + } + catch (Exception e) { + logger.error("Error occurred while sending bulk request.", e); + } + finally { + this.sender.clearList(); + } } + } - /** - * This method checks if the test mode is valid - * - * @param mode - * The test mode as String - */ - private void checkTestMode(String mode) { - if (!this.modes.contains(mode)) { - logger.warn( - "The parameter \"es.test.mode\" isn't set properly. Three modes are allowed: debug ,info, and quiet."); - logger.warn( - " -- \"debug\": sends request and response details to ElasticSearch. Info only sends the details if the response has an error."); - logger.warn(" -- \"info\": should be used in production"); - logger.warn(" -- \"error\": should be used if you."); - logger.warn(" -- \"quiet\": should be used if you don't care to have the details."); - } + @Override + public void teardownTest(BackendListenerContext context) throws Exception { + if (this.sender.getListSize() > 0) { + this.sender.sendRequest(this.esVersion); } + this.sender.closeConnection(); + super.teardownTest(context); + } - /** - * This method will validate the current sample to see if it is part of the filters or not. - * - * @param context - * The Backend Listener's context - * @param sr - * The current SampleResult - * @return true or false depending on whether or not the sample is valid - */ - private boolean validateSample(BackendListenerContext context, SampleResult sr) { - boolean valid = true; - String sampleLabel = sr.getSampleLabel().toLowerCase().trim(); + /** + * This method checks if the test mode is valid + * + * @param mode The test mode as String + */ + private void checkTestMode(String mode) { + if (!this.modes.contains(mode)) { + logger.warn( + "The parameter \"es.test.mode\" isn't set properly. Three modes are allowed: debug ,info, and quiet."); + logger.warn( + " -- \"debug\": sends request and response details to ElasticSearch. Info only sends the details if the response has an error."); + logger.warn(" -- \"info\": should be used in production"); + logger.warn(" -- \"error\": should be used if you."); + logger.warn(" -- \"quiet\": should be used if you don't care to have the details."); + } + } - if (this.filters.size() > 0) { - for (String filter : filters) { - Pattern pattern = Pattern.compile(filter); - Matcher matcher = pattern.matcher(sampleLabel); + /** + * This method will validate the current sample to see if it is part of the filters or not. + * + * @param context The Backend Listener's context + * @param sr The current SampleResult + * @return true or false depending on whether or not the sample is valid + */ + private boolean validateSample(BackendListenerContext context, SampleResult sr) { + boolean valid = true; + String sampleLabel = sr.getSampleLabel().toLowerCase().trim(); - if (!sampleLabel.startsWith("!!") && (sampleLabel.contains(filter) || matcher.find())) { - valid = true; - break; - } else { - logger.warn(String.format("Invalidate sample label %s, it may start with '!!' or it may not contain filter %s. " + - "Check the %s property value in the JMX file. Leave it empty to increase validation", sampleLabel, filter, ES_SAMPLE_FILTER)); - valid = false; - } - } - } + if (this.filters.size() > 0) { + for (String filter : filters) { + Pattern pattern = Pattern.compile(filter); + Matcher matcher = pattern.matcher(sampleLabel); - // if sample is successful but test mode is "error" only - if (sr.isSuccessful() && context.getParameter(ES_TEST_MODE).trim().equalsIgnoreCase("error") && valid) { - valid = false; + if (!sampleLabel.startsWith("!!") && (sampleLabel.contains(filter) || matcher.find())) { + valid = true; + break; + } + else { + logger.warn(String.format("Invalidate sample label %s, it may start with '!!' or it may not contain filter %s. " + + "Check the %s property value in the JMX file. Leave it empty to increase validation", sampleLabel, filter, ES_SAMPLE_FILTER)); + valid = false; } + } + } - return valid; + // if sample is successful but test mode is "error" only + if (sr.isSuccessful() && context.getParameter(ES_TEST_MODE).trim().equalsIgnoreCase("error") && valid) { + valid = false; } + + return valid; + } } From 6a22e1f28ff27940848b35b5d087b8cc5e8dd54a Mon Sep 17 00:00:00 2001 From: Johnny Hujol Date: Wed, 8 Oct 2025 12:45:00 +0200 Subject: [PATCH 03/14] feat(logging): enhance sampler result logging with response length Signed-off-by: Johnny Hujol --- .../elasticsearch/ElasticsearchBackendClient.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java b/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java index cfbcf28..b61abc6 100644 --- a/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java +++ b/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java @@ -245,8 +245,9 @@ public void handleSampleResults(List results, BackendListenerConte for (SampleResult sr : results) { String respString = sr.getResponseDataAsString(); - respString = respString.substring(0, Math.min(100, respString.length())); - logger.info("Handle sampler result {}: {}", sr.getSampleLabel(), respString); + int endIndex = Math.min(100, respString.length()); + respString = respString.substring(0, endIndex); + logger.info("Handle sampler result ({}) {}: {}", endIndex, sr.getSampleLabel(), respString); ElasticSearchMetric metric = new ElasticSearchMetric(sr, context.getParameter(ES_TEST_MODE), context.getParameter(ES_TIMESTAMP), this.buildNumber, context.getBooleanParameter(ES_PARSE_REQ_HEADERS, false), From b4dbc1116d2b57a735767701c9f3be3ab9352ea9 Mon Sep 17 00:00:00 2001 From: Johnny Hujol Date: Wed, 8 Oct 2025 13:08:35 +0200 Subject: [PATCH 04/14] ref(logging): improve logging for invalid sampler results and filtering Signed-off-by: Johnny Hujol --- .../elasticsearch/ElasticsearchBackendClient.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java b/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java index b61abc6..a7eeec6 100644 --- a/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java +++ b/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java @@ -263,9 +263,6 @@ public void handleSampleResults(List results, BackendListenerConte e.printStackTrace(); } } - else { - logger.warn(String.format("Not valid sampler result %s: %s", sr.getSampleLabel(), respString)); - } } if (this.sender.getListSize() >= this.bulkSize) { @@ -328,7 +325,7 @@ private boolean validateSample(BackendListenerContext context, SampleResult sr) break; } else { - logger.warn(String.format("Invalidate sample label %s, it may start with '!!' or it may not contain filter %s. " + + logger.warn(String.format("Filter out sample label %s, it may start with '!!' or it may not match the filter %s. " + "Check the %s property value in the JMX file. Leave it empty to increase validation", sampleLabel, filter, ES_SAMPLE_FILTER)); valid = false; } From 4736ead3c6e3061096b83207cb67e25d36894b39 Mon Sep 17 00:00:00 2001 From: Johnny Hujol Date: Thu, 9 Oct 2025 10:13:36 +0200 Subject: [PATCH 05/14] ref(logging): improve sample validation logic for clarity Signed-off-by: Johnny Hujol --- .../elasticsearch/ElasticsearchBackendClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java b/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java index a7eeec6..dc3d542 100644 --- a/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java +++ b/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java @@ -309,13 +309,13 @@ private void checkTestMode(String mode) { * * @param context The Backend Listener's context * @param sr The current SampleResult - * @return true or false depending on whether or not the sample is valid + * @return true or false depending on whether the sample is valid */ private boolean validateSample(BackendListenerContext context, SampleResult sr) { boolean valid = true; String sampleLabel = sr.getSampleLabel().toLowerCase().trim(); - if (this.filters.size() > 0) { + if (!this.filters.isEmpty()) { for (String filter : filters) { Pattern pattern = Pattern.compile(filter); Matcher matcher = pattern.matcher(sampleLabel); From b10a53e5795076619e2ec91ba1decda79c3e5427 Mon Sep 17 00:00:00 2001 From: Johnny Hujol Date: Thu, 9 Oct 2025 10:14:08 +0200 Subject: [PATCH 06/14] ref(docs): correct typos in README for clarity Signed-off-by: Johnny Hujol --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bd9bf63..fac737f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ [![Codacy Badge](https://api.codacy.com/project/badge/Grade/8a2f2a06171248acb6411a2d870558c8)](https://app.codacy.com/app/antho325/jmeter-elasticsearch-backend-listener?utm_source=github.com&utm_medium=referral&utm_content=delirius325/jmeter-elasticsearch-backend-listener&utm_campaign=Badge_Grade_Dashboard) [![Build Status](https://travis-ci.org/delirius325/jmeter-elasticsearch-backend-listener.svg?branch=master)](https://travis-ci.org/delirius325/jmeter-elasticsearch-backend-listener) - # Overview ### Description JMeter ElasticSearch Backend Listener is a JMeter plugin enabling you to send test results to an ElasticSearch engine. It is meant as an alternative live-monitoring tool to the built-in "InfluxDB" backend listener of JMeter. @@ -11,7 +10,7 @@ JMeter ElasticSearch Backend Listener is a JMeter plugin enabling you to send te * ElasticSearch low-level REST client * Using the low-level client makes the plugin compatible with any ElasticSearch version * X-Pack Authentication! - * Just supply your crendentials in the specified fields! + * Just supply your credentials in the specified fields! * Bulk requests * By making bulk requests, there are practically no impacts on the performance of the tests themselves. * Filters @@ -46,7 +45,7 @@ JMeter ElasticSearch Backend Listener is a JMeter plugin enabling you to send te * __info__ : Sends all samplers to the ElasticSearch engine, but only sends the headers, body info for the failed samplers. * __quiet__ : Only sends the response time, bytes, and other metrics * __error__ : Only sends the failing samplers to the ElasticSearch engine (Along with their headers and body information). -* Use either Kibana or Grafana to vizualize your results! +* Use either Kibana or Grafana to visualize your results! * [Click here to get a sample Grafana dashboard!](https://github.com/delirius325/jmeter-elasticsearch-backend-listener/wiki/JMeter-Generic-Dashboard) - All you need to do is import it into Grafana and change the data source! * Continuous Integration support - [Build comparison!](https://github.com/delirius325/jmeter-elasticsearch-backend-listener/wiki/Continuous-Integration---Build-Comparison) * Send JMeter variables to ElasticSearch! [Refer to this for more info!](https://github.com/delirius325/jmeter-elasticsearch-backend-listener/wiki/Sending-JMeter-variables) From 110a11fdeb6aff37124395fc98fc82ebd8ce1a99 Mon Sep 17 00:00:00 2001 From: Johnny Hujol Date: Thu, 9 Oct 2025 10:19:01 +0200 Subject: [PATCH 07/14] ref(logging): improve filter matching logic in ElasticsearchBackendClient Signed-off-by: Johnny Hujol --- .../elasticsearch/ElasticsearchBackendClient.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java b/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java index dc3d542..37168c3 100644 --- a/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java +++ b/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java @@ -317,11 +317,13 @@ private boolean validateSample(BackendListenerContext context, SampleResult sr) if (!this.filters.isEmpty()) { for (String filter : filters) { - Pattern pattern = Pattern.compile(filter); + Pattern pattern = Pattern.compile(String.format(".*%s.*", filter)); Matcher matcher = pattern.matcher(sampleLabel); - if (!sampleLabel.startsWith("!!") && (sampleLabel.contains(filter) || matcher.find())) { - valid = true; + if (matcher.find()) { + // README.md#Features*Filters + // You can also choose to exclude certain samplers; `!!exclude_this;filter1;filter2` + valid = !sampleLabel.startsWith("!!"); break; } else { From dfef9eedff40783155a56c38f04d72d17cb5743e Mon Sep 17 00:00:00 2001 From: Johnny Hujol Date: Thu, 9 Oct 2025 16:06:34 +0200 Subject: [PATCH 08/14] ref(logging): enhance logging for dynamic ES sample filters and improve parameter conversion Signed-off-by: Johnny Hujol --- README.md | 1 + .../ElasticsearchBackendClient.java | 35 +++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fac737f..bece3d9 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ JMeter ElasticSearch Backend Listener is a JMeter plugin enabling you to send te * Filters * Only send the samples you want by using Filters! Simply type them as follows in the field ``es.sample.filter`` : ``filter1;filter2;filter3`` or ``sampleLabel_must_contain_this``. * You can also choose to exclude certain samplers; `!!exclude_this;filter1;filter2` + * SYS_DYNAMIC_ES_PER_SAMPLE_FILTER is a System property that can be set before the ElasticsearchBackendClient#handleSampleResults(...) is called. It will override the es.sample.filter value set in the backendlistener configuration. * Specific fields ```field1;field2;field3` * Specify fields that you want to send to ElasticSearch (possible fields below) * AllThreads diff --git a/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java b/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java index 37168c3..20cae12 100644 --- a/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java +++ b/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java @@ -173,6 +173,18 @@ public void onFailure(Node node) { } } + /** + * Method that converts a semicolon separated list contained in a System property {@link System#getProperty(String)} into a string set + * + * @param propertyName + * @param set + */ + private void convertSystemPropertyToSet(String propertyName, Set set) { + String value = System.getProperty(propertyName); + + convertValueToSet(propertyName, set, value); + } + /** * Method that converts a semicolon separated list contained in a parameter into a string set * @@ -181,8 +193,14 @@ public void onFailure(Node node) { * @param set */ private void convertParameterToSet(BackendListenerContext context, String parameter, Set set) { - String[] array = (context.getParameter(parameter).contains(";")) ? context.getParameter(parameter).split(";") - : new String[]{context.getParameter(parameter)}; + String value = context.getParameter(parameter); + + convertValueToSet(parameter, set, value); + } + + private static void convertValueToSet(String parameter, Set set, String value) { + String[] array = (value.contains(";")) ? value.split(";") + : new String[]{value}; if (array.length > 0 && !array[0].trim().equals("")) { for (String entry : array) { set.add(entry.toLowerCase().trim()); @@ -243,6 +261,15 @@ public void handleSampleResults(List results, BackendListenerConte return; } + if (System.getProperty("SYS_DYNAMIC_ES_PER_SAMPLE_FILTER") != null) { + // Override filters from ES_SAMPLE_FILTER if exists + this.filters.clear(); + logger.info("============> Property SYS_DYNAMIC_ES_PER_SAMPLE_FILTER filters: {}", System.getProperty("SYS_DYNAMIC_ES_PER_SAMPLE_FILTER")); + convertSystemPropertyToSet("SYS_DYNAMIC_ES_PER_SAMPLE_FILTER", this.filters); + logger.info("============> Property SYS_DYNAMIC_ES_PER_SAMPLE_FILTER filters: {}", this.filters); + } + + final String filtersAsString = (this.filters.isEmpty()) ? "no filters" : String.join(", ", this.filters); for (SampleResult sr : results) { String respString = sr.getResponseDataAsString(); int endIndex = Math.min(100, respString.length()); @@ -262,6 +289,10 @@ public void handleSampleResults(List results, BackendListenerConte "The ElasticSearch Backend Listener was unable to add sampler to the list of samplers to send... More info in JMeter's console."); e.printStackTrace(); } + logger.info("Sampler result processed {} with filters {}", sr.getSampleLabel(), filtersAsString); + } + else { + logger.info("Sampler result skipped {} with filters {}", sr.getSampleLabel(), filtersAsString); } } From 287a0405c4b327a36444385b9f27277fa027132f Mon Sep 17 00:00:00 2001 From: Johnny Hujol Date: Mon, 13 Oct 2025 12:53:42 +0200 Subject: [PATCH 09/14] ref(logging): add error logging for SampleResult with no response data Signed-off-by: Johnny Hujol --- .../ElasticsearchBackendClient.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java b/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java index 20cae12..c2035ea 100644 --- a/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java +++ b/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java @@ -272,6 +272,24 @@ public void handleSampleResults(List results, BackendListenerConte final String filtersAsString = (this.filters.isEmpty()) ? "no filters" : String.join(", ", this.filters); for (SampleResult sr : results) { String respString = sr.getResponseDataAsString(); + if (sr.getBytesAsLong() == 0 || respString == null) { + logger.error("SampleResult has no response data. Details: label={}, success={}, responseCode={}, responseMessage={}, " + + "startTime={}, endTime={}, threadName={}, bytes={}, dataType={}, samplerData={}, requestHeaders={}, responseHeaders={}", + sr.getSampleLabel(), + sr.isSuccessful(), + sr.getResponseCode(), + sr.getResponseMessage(), + sr.getStartTime(), + sr.getEndTime(), + sr.getThreadName(), + sr.getBytesAsLong(), + sr.getDataType(), + sr.getSamplerData(), + sr.getRequestHeaders(), + sr.getResponseHeaders()); + throw new IllegalStateException("SampleResult has no response data for label: " + sr.getSampleLabel()); + } + int endIndex = Math.min(100, respString.length()); respString = respString.substring(0, endIndex); logger.info("Handle sampler result ({}) {}: {}", endIndex, sr.getSampleLabel(), respString); From 3a2bc348000b524ed3600a45b93d8adba20d2574 Mon Sep 17 00:00:00 2001 From: Johnny Hujol Date: Mon, 13 Oct 2025 12:56:41 +0200 Subject: [PATCH 10/14] ref(pom): update version to 2.7.1-itudoben.1 for improved tracking Signed-off-by: Johnny Hujol --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index db22c9e..41bf235 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 io.github.delirius325 jmeter.backendlistener.elasticsearch - 2.7.1 + 2.7.1-itudoben.1 jar jmeter.backendlistener.elasticsearch From 2aa112875faf6c21895028e008e3a52c61db5e50 Mon Sep 17 00:00:00 2001 From: Johnny Hujol Date: Mon, 13 Oct 2025 13:06:53 +0200 Subject: [PATCH 11/14] ref(pom): update version to 2.7.1 for consistency Signed-off-by: Johnny Hujol --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 41bf235..db22c9e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 io.github.delirius325 jmeter.backendlistener.elasticsearch - 2.7.1-itudoben.1 + 2.7.1 jar jmeter.backendlistener.elasticsearch From 38da0ee0a7fffcc07cd85d7cfb59ac12bbdb3d6c Mon Sep 17 00:00:00 2001 From: Johnny Hujol Date: Mon, 13 Oct 2025 13:16:57 +0200 Subject: [PATCH 12/14] ref(backendlistenerclient): improve code formatting Signed-off-by: Johnny Hujol --- .../ElasticsearchBackendClient.java | 664 +++++++++--------- 1 file changed, 328 insertions(+), 336 deletions(-) diff --git a/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java b/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java index c2035ea..f25ead2 100644 --- a/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java +++ b/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java @@ -32,362 +32,354 @@ public class ElasticsearchBackendClient extends AbstractBackendListenerClient { - private static final String BUILD_NUMBER = "BuildNumber"; - private static final String ES_SCHEME = "es.scheme"; - private static final String ES_HOST = "es.host"; - private static final String ES_PORT = "es.port"; - private static final String ES_INDEX = "es.index"; - private static final String ES_FIELDS = "es.fields"; - private static final String ES_TIMESTAMP = "es.timestamp"; - private static final String ES_BULK_SIZE = "es.bulk.size"; - private static final String ES_TIMEOUT_MS = "es.timout.ms"; - private static final String ES_SAMPLE_FILTER = "es.sample.filter"; - private static final String ES_TEST_MODE = "es.test.mode"; - private static final String ES_AUTH_USER = "es.xpack.user"; - private static final String ES_AUTH_PWD = "es.xpack.password"; - private static final String ES_PARSE_REQ_HEADERS = "es.parse.all.req.headers"; - private static final String ES_PARSE_RES_HEADERS = "es.parse.all.res.headers"; - private static final String ES_AWS_ENDPOINT = "es.aws.endpoint"; - private static final String ES_AWS_REGION = "es.aws.region"; - private static final String ES_SSL_TRUSTSTORE_PATH = "es.ssl.truststore.path"; - private static final String ES_SSL_TRUSTSTORE_PW = "es.ssl.truststore.pw"; - private static final String ES_SSL_KEYSTORE_PATH = "es.ssl.keystore.path"; - private static final String ES_SSL_KEYSTORE_PW = "es.ssl.keystore.pw"; - private static final String ES_SSL_VERIFICATION_MODE = "es.ssl.verificationMode"; - private static final long DEFAULT_TIMEOUT_MS = 200L; - private static final String SERVICE_NAME = "es"; - private static RestClient client; - private static final Logger logger = LoggerFactory.getLogger(ElasticsearchBackendClient.class); - private static final AWSCredentialsProvider credentialsProvider = new DefaultAWSCredentialsProviderChain(); - private static final Map DEFAULT_ARGS = new LinkedHashMap<>(); - - static { - DEFAULT_ARGS.put(ES_SCHEME, "http"); - DEFAULT_ARGS.put(ES_HOST, null); - DEFAULT_ARGS.put(ES_PORT, "9200"); - DEFAULT_ARGS.put(ES_INDEX, null); - DEFAULT_ARGS.put(ES_TIMESTAMP, "yyyy-MM-dd'T'HH:mm:ss.SSSZZ"); - DEFAULT_ARGS.put(ES_BULK_SIZE, "100"); - DEFAULT_ARGS.put(ES_TIMEOUT_MS, Long.toString(DEFAULT_TIMEOUT_MS)); - DEFAULT_ARGS.put(ES_SAMPLE_FILTER, null); - DEFAULT_ARGS.put(ES_FIELDS, null); - DEFAULT_ARGS.put(ES_TEST_MODE, "info"); - DEFAULT_ARGS.put(ES_AUTH_USER, ""); - DEFAULT_ARGS.put(ES_AUTH_PWD, ""); - DEFAULT_ARGS.put(ES_PARSE_REQ_HEADERS, "false"); - DEFAULT_ARGS.put(ES_PARSE_RES_HEADERS, "false"); - DEFAULT_ARGS.put(ES_AWS_ENDPOINT, ""); - DEFAULT_ARGS.put(ES_AWS_REGION, ""); - DEFAULT_ARGS.put(ES_SSL_TRUSTSTORE_PATH, ""); - DEFAULT_ARGS.put(ES_SSL_TRUSTSTORE_PW, ""); - DEFAULT_ARGS.put(ES_SSL_KEYSTORE_PATH, ""); - DEFAULT_ARGS.put(ES_SSL_KEYSTORE_PW, ""); - DEFAULT_ARGS.put(ES_SSL_VERIFICATION_MODE, "full"); - } - - private ElasticSearchMetricSender sender; - private Set modes; - private Set filters; - private Set fields; - private int buildNumber; - private int bulkSize; - private int esVersion; - private long timeoutMs; - - private static final TrustStrategy TRUST_ALL_STRATEGY = (chain, authType) -> true; - - @Override - public Arguments getDefaultParameters() { - Arguments arguments = new Arguments(); - DEFAULT_ARGS.forEach(arguments::addArgument); - return arguments; - } - - @Override - public void setupTest(BackendListenerContext context) throws Exception { - try { - this.filters = new HashSet<>(); - this.fields = new HashSet<>(); - this.modes = new HashSet<>(Arrays.asList("info", "debug", "error", "quiet")); - this.bulkSize = Integer.parseInt(context.getParameter(ES_BULK_SIZE)); - this.timeoutMs = Integer.parseInt((context.getParameter(ES_TIMEOUT_MS))); - this.buildNumber = (JMeterUtils.getProperty(ElasticsearchBackendClient.BUILD_NUMBER) != null - && !JMeterUtils.getProperty(ElasticsearchBackendClient.BUILD_NUMBER).trim().equals("")) - ? Integer.parseInt(JMeterUtils.getProperty(ElasticsearchBackendClient.BUILD_NUMBER)) : 0; - - setSSLConfiguration(context); - - if (context.getParameter(ES_AWS_ENDPOINT).equalsIgnoreCase("")) { - client = RestClient - .builder(new HttpHost(context.getParameter(ES_HOST), - Integer.parseInt(context.getParameter(ES_PORT)), context.getParameter(ES_SCHEME))) - .setHttpClientConfigCallback(httpAsyncClientBuilder -> { - if (context.getParameter(ES_SSL_VERIFICATION_MODE).equalsIgnoreCase("none")) { - logger.info("Will trust all remote SSL certificates."); - final SSLContextBuilder contextBuilder = new SSLContextBuilder(); - try { - contextBuilder.loadTrustMaterial(TRUST_ALL_STRATEGY); - httpAsyncClientBuilder.setSSLContext(contextBuilder.build()); - httpAsyncClientBuilder.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE); - } - catch (Exception e) { - // NOTE: purposedly ignored as this strategy does not use any custom algorithm - // or certificate - } - } - return httpAsyncClientBuilder; - }) - .setRequestConfigCallback(requestConfigBuilder -> requestConfigBuilder.setConnectTimeout(5000) - .setSocketTimeout((int) timeoutMs)) - .setFailureListener(new RestClient.FailureListener() { - @Override - public void onFailure(Node node) { - logger.error("Error with node: " + node.toString()); - } - }).build(); - } - else { - AWS4Signer signer = new AWS4Signer(); - signer.setServiceName(SERVICE_NAME); - signer.setRegionName(context.getParameter(ES_AWS_REGION)); - HttpRequestInterceptor interceptor = new AWSRequestSigningApacheInterceptor(SERVICE_NAME, signer, - credentialsProvider); - client = RestClient.builder(HttpHost.create(context.getParameter(ES_AWS_ENDPOINT))) - .setHttpClientConfigCallback(hacb -> hacb.addInterceptorLast(interceptor)).build(); - } - - convertParameterToSet(context, ES_SAMPLE_FILTER, this.filters); - convertParameterToSet(context, ES_FIELDS, this.fields); - - this.sender = new ElasticSearchMetricSender(client, context.getParameter(ES_INDEX).toLowerCase(), - context.getParameter(ES_AUTH_USER), context.getParameter(ES_AUTH_PWD), - context.getParameter(ES_AWS_ENDPOINT)); - this.sender.createIndex(); - this.esVersion = sender.getElasticSearchVersion(); - - checkTestMode(context.getParameter(ES_TEST_MODE)); - super.setupTest(context); - } - catch (Exception e) { - throw new IllegalStateException("Unable to connect to the ElasticSearch engine", e); + private static final String BUILD_NUMBER = "BuildNumber"; + private static final String ES_SCHEME = "es.scheme"; + private static final String ES_HOST = "es.host"; + private static final String ES_PORT = "es.port"; + private static final String ES_INDEX = "es.index"; + private static final String ES_FIELDS = "es.fields"; + private static final String ES_TIMESTAMP = "es.timestamp"; + private static final String ES_BULK_SIZE = "es.bulk.size"; + private static final String ES_TIMEOUT_MS = "es.timout.ms"; + private static final String ES_SAMPLE_FILTER = "es.sample.filter"; + private static final String ES_TEST_MODE = "es.test.mode"; + private static final String ES_AUTH_USER = "es.xpack.user"; + private static final String ES_AUTH_PWD = "es.xpack.password"; + private static final String ES_PARSE_REQ_HEADERS = "es.parse.all.req.headers"; + private static final String ES_PARSE_RES_HEADERS = "es.parse.all.res.headers"; + private static final String ES_AWS_ENDPOINT = "es.aws.endpoint"; + private static final String ES_AWS_REGION = "es.aws.region"; + private static final String ES_SSL_TRUSTSTORE_PATH = "es.ssl.truststore.path"; + private static final String ES_SSL_TRUSTSTORE_PW = "es.ssl.truststore.pw"; + private static final String ES_SSL_KEYSTORE_PATH = "es.ssl.keystore.path"; + private static final String ES_SSL_KEYSTORE_PW = "es.ssl.keystore.pw"; + private static final String ES_SSL_VERIFICATION_MODE = "es.ssl.verificationMode"; + private static final long DEFAULT_TIMEOUT_MS = 200L; + private static final String SERVICE_NAME = "es"; + private static RestClient client; + private static final Logger logger = LoggerFactory.getLogger(ElasticsearchBackendClient.class); + private static final AWSCredentialsProvider credentialsProvider = new DefaultAWSCredentialsProviderChain(); + private static final Map DEFAULT_ARGS = new LinkedHashMap<>(); + + static { + DEFAULT_ARGS.put(ES_SCHEME, "http"); + DEFAULT_ARGS.put(ES_HOST, null); + DEFAULT_ARGS.put(ES_PORT, "9200"); + DEFAULT_ARGS.put(ES_INDEX, null); + DEFAULT_ARGS.put(ES_TIMESTAMP, "yyyy-MM-dd'T'HH:mm:ss.SSSZZ"); + DEFAULT_ARGS.put(ES_BULK_SIZE, "100"); + DEFAULT_ARGS.put(ES_TIMEOUT_MS, Long.toString(DEFAULT_TIMEOUT_MS)); + DEFAULT_ARGS.put(ES_SAMPLE_FILTER, null); + DEFAULT_ARGS.put(ES_FIELDS, null); + DEFAULT_ARGS.put(ES_TEST_MODE, "info"); + DEFAULT_ARGS.put(ES_AUTH_USER, ""); + DEFAULT_ARGS.put(ES_AUTH_PWD, ""); + DEFAULT_ARGS.put(ES_PARSE_REQ_HEADERS, "false"); + DEFAULT_ARGS.put(ES_PARSE_RES_HEADERS, "false"); + DEFAULT_ARGS.put(ES_AWS_ENDPOINT, ""); + DEFAULT_ARGS.put(ES_AWS_REGION, ""); + DEFAULT_ARGS.put(ES_SSL_TRUSTSTORE_PATH, ""); + DEFAULT_ARGS.put(ES_SSL_TRUSTSTORE_PW, ""); + DEFAULT_ARGS.put(ES_SSL_KEYSTORE_PATH, ""); + DEFAULT_ARGS.put(ES_SSL_KEYSTORE_PW, ""); + DEFAULT_ARGS.put(ES_SSL_VERIFICATION_MODE, "full"); } - } - - /** - * Method that converts a semicolon separated list contained in a System property {@link System#getProperty(String)} into a string set - * - * @param propertyName - * @param set - */ - private void convertSystemPropertyToSet(String propertyName, Set set) { - String value = System.getProperty(propertyName); - - convertValueToSet(propertyName, set, value); - } - - /** - * Method that converts a semicolon separated list contained in a parameter into a string set - * - * @param context - * @param parameter - * @param set - */ - private void convertParameterToSet(BackendListenerContext context, String parameter, Set set) { - String value = context.getParameter(parameter); - - convertValueToSet(parameter, set, value); - } - - private static void convertValueToSet(String parameter, Set set, String value) { - String[] array = (value.contains(";")) ? value.split(";") - : new String[]{value}; - if (array.length > 0 && !array[0].trim().equals("")) { - for (String entry : array) { - set.add(entry.toLowerCase().trim()); - if (logger.isDebugEnabled()) - logger.debug("Parsed from " + parameter + ": " + entry.toLowerCase().trim()); - } - } - } - - /** - * Method that sets the SSL configuration to be able to send requests to a secured endpoint - * - * @param context - */ - private void setSSLConfiguration(BackendListenerContext context) { - String keyStorePath = context.getParameter(ES_SSL_KEYSTORE_PATH); - if (!keyStorePath.equalsIgnoreCase("")) { - logger.warn("KeyStore system properties overwritten by ES SSL configuration."); - System.setProperty("javax.net.ssl.keyStore", keyStorePath); - System.setProperty("javax.net.ssl.keyStorePassword", context.getParameter(ES_SSL_KEYSTORE_PW)); - switch (FilenameUtils.getExtension(keyStorePath)) { - case "jks": - System.setProperty("javax.net.ssl.keyStoreType", "jks"); - break; - case "p12": - System.setProperty("javax.net.ssl.keyStoreType", "pkcs12"); - break; - default: - System.setProperty("javax.net.ssl.keyStoreType", ""); - break; - } + + private ElasticSearchMetricSender sender; + private Set modes; + private Set filters; + private Set fields; + private int buildNumber; + private int bulkSize; + private int esVersion; + private long timeoutMs; + + private static final TrustStrategy TRUST_ALL_STRATEGY = (chain, authType) -> true; + + @Override + public Arguments getDefaultParameters() { + Arguments arguments = new Arguments(); + DEFAULT_ARGS.forEach(arguments::addArgument); + return arguments; } - String trustStorePath = context.getParameter(ES_SSL_TRUSTSTORE_PATH); - if (!trustStorePath.equalsIgnoreCase("")) { - logger.warn("TrustStore system properties overwritten by ES SSL configuration."); - System.setProperty("javax.net.ssl.trustStore", trustStorePath); - System.setProperty("javax.net.ssl.trustStorePassword", context.getParameter(ES_SSL_TRUSTSTORE_PW)); - switch (FilenameUtils.getExtension(trustStorePath)) { - case "jks": - System.setProperty("javax.net.ssl.trustStoreType", "jks"); - break; - case "p12": - System.setProperty("javax.net.ssl.trustStoreType", "pkcs12"); - break; - default: - System.setProperty("javax.net.ssl.trustStoreType", ""); - break; - } + @Override + public void setupTest(BackendListenerContext context) throws Exception { + try { + this.filters = new HashSet<>(); + this.fields = new HashSet<>(); + this.modes = new HashSet<>(Arrays.asList("info", "debug", "error", "quiet")); + this.bulkSize = Integer.parseInt(context.getParameter(ES_BULK_SIZE)); + this.timeoutMs = Integer.parseInt((context.getParameter(ES_TIMEOUT_MS))); + this.buildNumber = (JMeterUtils.getProperty(ElasticsearchBackendClient.BUILD_NUMBER) != null + && !JMeterUtils.getProperty(ElasticsearchBackendClient.BUILD_NUMBER).trim().equals("")) + ? Integer.parseInt(JMeterUtils.getProperty(ElasticsearchBackendClient.BUILD_NUMBER)) : 0; + + setSSLConfiguration(context); + + if (context.getParameter(ES_AWS_ENDPOINT).equalsIgnoreCase("")) { + client = RestClient + .builder(new HttpHost(context.getParameter(ES_HOST), + Integer.parseInt(context.getParameter(ES_PORT)), context.getParameter(ES_SCHEME))) + .setHttpClientConfigCallback(httpAsyncClientBuilder -> { + if (context.getParameter(ES_SSL_VERIFICATION_MODE).equalsIgnoreCase("none")) { + logger.info("Will trust all remote SSL certificates."); + final SSLContextBuilder contextBuilder = new SSLContextBuilder(); + try { + contextBuilder.loadTrustMaterial(TRUST_ALL_STRATEGY); + httpAsyncClientBuilder.setSSLContext(contextBuilder.build()); + httpAsyncClientBuilder.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE); + } catch (Exception e) { + // NOTE: purposedly ignored as this strategy does not use any custom algorithm + // or certificate + } + } + return httpAsyncClientBuilder; + }) + .setRequestConfigCallback(requestConfigBuilder -> requestConfigBuilder.setConnectTimeout(5000) + .setSocketTimeout((int) timeoutMs)) + .setFailureListener(new RestClient.FailureListener() { + @Override + public void onFailure(Node node) { + logger.error("Error with node: " + node.toString()); + } + }).build(); + } else { + AWS4Signer signer = new AWS4Signer(); + signer.setServiceName(SERVICE_NAME); + signer.setRegionName(context.getParameter(ES_AWS_REGION)); + HttpRequestInterceptor interceptor = new AWSRequestSigningApacheInterceptor(SERVICE_NAME, signer, + credentialsProvider); + client = RestClient.builder(HttpHost.create(context.getParameter(ES_AWS_ENDPOINT))) + .setHttpClientConfigCallback(hacb -> hacb.addInterceptorLast(interceptor)).build(); + } + + convertParameterToSet(context, ES_SAMPLE_FILTER, this.filters); + convertParameterToSet(context, ES_FIELDS, this.fields); + + this.sender = new ElasticSearchMetricSender(client, context.getParameter(ES_INDEX).toLowerCase(), + context.getParameter(ES_AUTH_USER), context.getParameter(ES_AUTH_PWD), + context.getParameter(ES_AWS_ENDPOINT)); + this.sender.createIndex(); + this.esVersion = sender.getElasticSearchVersion(); + + checkTestMode(context.getParameter(ES_TEST_MODE)); + super.setupTest(context); + } catch (Exception e) { + throw new IllegalStateException("Unable to connect to the ElasticSearch engine", e); + } } - } + /** + * Method that converts a semicolon separated list contained in a System property {@link System#getProperty(String)} into a string set + * + * @param propertyName + * @param set + */ + private void convertSystemPropertyToSet(String propertyName, Set set) { + String value = System.getProperty(propertyName); - @Override - public void handleSampleResults(List results, BackendListenerContext context) { - if (results.isEmpty()) { - logger.warn("There are no sampler results to handle!"); - return; + convertValueToSet(propertyName, set, value); } - if (System.getProperty("SYS_DYNAMIC_ES_PER_SAMPLE_FILTER") != null) { - // Override filters from ES_SAMPLE_FILTER if exists - this.filters.clear(); - logger.info("============> Property SYS_DYNAMIC_ES_PER_SAMPLE_FILTER filters: {}", System.getProperty("SYS_DYNAMIC_ES_PER_SAMPLE_FILTER")); - convertSystemPropertyToSet("SYS_DYNAMIC_ES_PER_SAMPLE_FILTER", this.filters); - logger.info("============> Property SYS_DYNAMIC_ES_PER_SAMPLE_FILTER filters: {}", this.filters); + /** + * Method that converts a semicolon separated list contained in a parameter into a string set + * + * @param context + * @param parameter + * @param set + */ + private void convertParameterToSet(BackendListenerContext context, String parameter, Set set) { + String value = context.getParameter(parameter); + + convertValueToSet(parameter, set, value); } - final String filtersAsString = (this.filters.isEmpty()) ? "no filters" : String.join(", ", this.filters); - for (SampleResult sr : results) { - String respString = sr.getResponseDataAsString(); - if (sr.getBytesAsLong() == 0 || respString == null) { - logger.error("SampleResult has no response data. Details: label={}, success={}, responseCode={}, responseMessage={}, " + - "startTime={}, endTime={}, threadName={}, bytes={}, dataType={}, samplerData={}, requestHeaders={}, responseHeaders={}", - sr.getSampleLabel(), - sr.isSuccessful(), - sr.getResponseCode(), - sr.getResponseMessage(), - sr.getStartTime(), - sr.getEndTime(), - sr.getThreadName(), - sr.getBytesAsLong(), - sr.getDataType(), - sr.getSamplerData(), - sr.getRequestHeaders(), - sr.getResponseHeaders()); - throw new IllegalStateException("SampleResult has no response data for label: " + sr.getSampleLabel()); - } - - int endIndex = Math.min(100, respString.length()); - respString = respString.substring(0, endIndex); - logger.info("Handle sampler result ({}) {}: {}", endIndex, sr.getSampleLabel(), respString); - ElasticSearchMetric metric = new ElasticSearchMetric(sr, context.getParameter(ES_TEST_MODE), - context.getParameter(ES_TIMESTAMP), this.buildNumber, - context.getBooleanParameter(ES_PARSE_REQ_HEADERS, false), - context.getBooleanParameter(ES_PARSE_RES_HEADERS, false), fields); - - if (validateSample(context, sr)) { - try { - this.sender.addToList(new Gson().toJson(metric.getMetric(context))); + private static void convertValueToSet(String parameter, Set set, String value) { + String[] array = (value.contains(";")) ? value.split(";") + : new String[]{value}; + if (array.length > 0 && !array[0].trim().equals("")) { + for (String entry : array) { + set.add(entry.toLowerCase().trim()); + if (logger.isDebugEnabled()) + logger.debug("Parsed from " + parameter + ": " + entry.toLowerCase().trim()); + } + } + } + + /** + * Method that sets the SSL configuration to be able to send requests to a secured endpoint + * + * @param context + */ + private void setSSLConfiguration(BackendListenerContext context) { + String keyStorePath = context.getParameter(ES_SSL_KEYSTORE_PATH); + if (!keyStorePath.equalsIgnoreCase("")) { + logger.warn("KeyStore system properties overwritten by ES SSL configuration."); + System.setProperty("javax.net.ssl.keyStore", keyStorePath); + System.setProperty("javax.net.ssl.keyStorePassword", context.getParameter(ES_SSL_KEYSTORE_PW)); + switch (FilenameUtils.getExtension(keyStorePath)) { + case "jks": + System.setProperty("javax.net.ssl.keyStoreType", "jks"); + break; + case "p12": + System.setProperty("javax.net.ssl.keyStoreType", "pkcs12"); + break; + default: + System.setProperty("javax.net.ssl.keyStoreType", ""); + break; + } } - catch (Exception e) { - logger.error( - "The ElasticSearch Backend Listener was unable to add sampler to the list of samplers to send... More info in JMeter's console."); - e.printStackTrace(); + + String trustStorePath = context.getParameter(ES_SSL_TRUSTSTORE_PATH); + if (!trustStorePath.equalsIgnoreCase("")) { + logger.warn("TrustStore system properties overwritten by ES SSL configuration."); + System.setProperty("javax.net.ssl.trustStore", trustStorePath); + System.setProperty("javax.net.ssl.trustStorePassword", context.getParameter(ES_SSL_TRUSTSTORE_PW)); + switch (FilenameUtils.getExtension(trustStorePath)) { + case "jks": + System.setProperty("javax.net.ssl.trustStoreType", "jks"); + break; + case "p12": + System.setProperty("javax.net.ssl.trustStoreType", "pkcs12"); + break; + default: + System.setProperty("javax.net.ssl.trustStoreType", ""); + break; + } } - logger.info("Sampler result processed {} with filters {}", sr.getSampleLabel(), filtersAsString); - } - else { - logger.info("Sampler result skipped {} with filters {}", sr.getSampleLabel(), filtersAsString); - } + } - if (this.sender.getListSize() >= this.bulkSize) { - try { - this.sender.sendRequest(this.esVersion); - } - catch (Exception e) { - logger.error("Error occurred while sending bulk request.", e); - } - finally { - this.sender.clearList(); - } + @Override + public void handleSampleResults(List results, BackendListenerContext context) { + if (results.isEmpty()) { + logger.warn("There are no sampler results to handle!"); + return; + } + + if (System.getProperty("SYS_DYNAMIC_ES_PER_SAMPLE_FILTER") != null) { + // Override filters from ES_SAMPLE_FILTER if exists + this.filters.clear(); + logger.info("============> Property SYS_DYNAMIC_ES_PER_SAMPLE_FILTER filters: {}", System.getProperty("SYS_DYNAMIC_ES_PER_SAMPLE_FILTER")); + convertSystemPropertyToSet("SYS_DYNAMIC_ES_PER_SAMPLE_FILTER", this.filters); + logger.info("============> Property SYS_DYNAMIC_ES_PER_SAMPLE_FILTER filters: {}", this.filters); + } + + final String filtersAsString = (this.filters.isEmpty()) ? "no filters" : String.join(", ", this.filters); + for (SampleResult sr : results) { + String respString = sr.getResponseDataAsString(); + if (sr.getBytesAsLong() == 0 || respString == null) { + logger.error("SampleResult has no response data. Details: label={}, success={}, responseCode={}, responseMessage={}, " + + "startTime={}, endTime={}, threadName={}, bytes={}, dataType={}, samplerData={}, requestHeaders={}, responseHeaders={}", + sr.getSampleLabel(), + sr.isSuccessful(), + sr.getResponseCode(), + sr.getResponseMessage(), + sr.getStartTime(), + sr.getEndTime(), + sr.getThreadName(), + sr.getBytesAsLong(), + sr.getDataType(), + sr.getSamplerData(), + sr.getRequestHeaders(), + sr.getResponseHeaders()); + throw new IllegalStateException("SampleResult has no response data for label: " + sr.getSampleLabel()); + } + + int endIndex = Math.min(100, respString.length()); + respString = respString.substring(0, endIndex); + logger.info("Handle sampler result ({}) {}: {}", endIndex, sr.getSampleLabel(), respString); + ElasticSearchMetric metric = new ElasticSearchMetric(sr, context.getParameter(ES_TEST_MODE), + context.getParameter(ES_TIMESTAMP), this.buildNumber, + context.getBooleanParameter(ES_PARSE_REQ_HEADERS, false), + context.getBooleanParameter(ES_PARSE_RES_HEADERS, false), fields); + + if (validateSample(context, sr)) { + try { + this.sender.addToList(new Gson().toJson(metric.getMetric(context))); + } catch (Exception e) { + logger.error( + "The ElasticSearch Backend Listener was unable to add sampler to the list of samplers to send... More info in JMeter's console."); + e.printStackTrace(); + } + logger.info("Sampler result processed {} with filters {}", sr.getSampleLabel(), filtersAsString); + } else { + logger.info("Sampler result skipped {} with filters {}", sr.getSampleLabel(), filtersAsString); + } + } + + if (this.sender.getListSize() >= this.bulkSize) { + try { + this.sender.sendRequest(this.esVersion); + } catch (Exception e) { + logger.error("Error occurred while sending bulk request.", e); + } finally { + this.sender.clearList(); + } + } } - } - @Override - public void teardownTest(BackendListenerContext context) throws Exception { - if (this.sender.getListSize() > 0) { - this.sender.sendRequest(this.esVersion); + @Override + public void teardownTest(BackendListenerContext context) throws Exception { + if (this.sender.getListSize() > 0) { + this.sender.sendRequest(this.esVersion); + } + this.sender.closeConnection(); + super.teardownTest(context); } - this.sender.closeConnection(); - super.teardownTest(context); - } - - /** - * This method checks if the test mode is valid - * - * @param mode The test mode as String - */ - private void checkTestMode(String mode) { - if (!this.modes.contains(mode)) { - logger.warn( - "The parameter \"es.test.mode\" isn't set properly. Three modes are allowed: debug ,info, and quiet."); - logger.warn( - " -- \"debug\": sends request and response details to ElasticSearch. Info only sends the details if the response has an error."); - logger.warn(" -- \"info\": should be used in production"); - logger.warn(" -- \"error\": should be used if you."); - logger.warn(" -- \"quiet\": should be used if you don't care to have the details."); + + /** + * This method checks if the test mode is valid + * + * @param mode The test mode as String + */ + private void checkTestMode(String mode) { + if (!this.modes.contains(mode)) { + logger.warn( + "The parameter \"es.test.mode\" isn't set properly. Three modes are allowed: debug ,info, and quiet."); + logger.warn( + " -- \"debug\": sends request and response details to ElasticSearch. Info only sends the details if the response has an error."); + logger.warn(" -- \"info\": should be used in production"); + logger.warn(" -- \"error\": should be used if you."); + logger.warn(" -- \"quiet\": should be used if you don't care to have the details."); + } } - } - - /** - * This method will validate the current sample to see if it is part of the filters or not. - * - * @param context The Backend Listener's context - * @param sr The current SampleResult - * @return true or false depending on whether the sample is valid - */ - private boolean validateSample(BackendListenerContext context, SampleResult sr) { - boolean valid = true; - String sampleLabel = sr.getSampleLabel().toLowerCase().trim(); - - if (!this.filters.isEmpty()) { - for (String filter : filters) { - Pattern pattern = Pattern.compile(String.format(".*%s.*", filter)); - Matcher matcher = pattern.matcher(sampleLabel); - - if (matcher.find()) { - // README.md#Features*Filters - // You can also choose to exclude certain samplers; `!!exclude_this;filter1;filter2` - valid = !sampleLabel.startsWith("!!"); - break; + + /** + * This method will validate the current sample to see if it is part of the filters or not. + * + * @param context The Backend Listener's context + * @param sr The current SampleResult + * @return true or false depending on whether the sample is valid + */ + private boolean validateSample(BackendListenerContext context, SampleResult sr) { + boolean valid = true; + String sampleLabel = sr.getSampleLabel().toLowerCase().trim(); + + if (!this.filters.isEmpty()) { + for (String filter : filters) { + Pattern pattern = Pattern.compile(String.format(".*%s.*", filter)); + Matcher matcher = pattern.matcher(sampleLabel); + + if (matcher.find()) { + // README.md#Features*Filters + // You can also choose to exclude certain samplers; `!!exclude_this;filter1;filter2` + valid = !sampleLabel.startsWith("!!"); + break; + } else { + logger.warn(String.format("Filter out sample label %s, it may start with '!!' or it may not match the filter %s. " + + "Check the %s property value in the JMX file. Leave it empty to increase validation", sampleLabel, filter, ES_SAMPLE_FILTER)); + valid = false; + } + } } - else { - logger.warn(String.format("Filter out sample label %s, it may start with '!!' or it may not match the filter %s. " + - "Check the %s property value in the JMX file. Leave it empty to increase validation", sampleLabel, filter, ES_SAMPLE_FILTER)); - valid = false; + + // if sample is successful but test mode is "error" only + if (sr.isSuccessful() && context.getParameter(ES_TEST_MODE).trim().equalsIgnoreCase("error") && valid) { + valid = false; } - } - } - // if sample is successful but test mode is "error" only - if (sr.isSuccessful() && context.getParameter(ES_TEST_MODE).trim().equalsIgnoreCase("error") && valid) { - valid = false; + return valid; } - - return valid; - } } From 40a24f7c72abd8a6a853f3fe50518f2fb9402b3f Mon Sep 17 00:00:00 2001 From: Johnny Hujol Date: Mon, 13 Oct 2025 15:05:45 +0200 Subject: [PATCH 13/14] ref(pom): update version to 2.7.1-sirenbk.1 for improved tracking Signed-off-by: Johnny Hujol --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index db22c9e..9bd4be7 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 io.github.delirius325 jmeter.backendlistener.elasticsearch - 2.7.1 + 2.7.1-sirenbk.1 jar jmeter.backendlistener.elasticsearch From b107ce3a09a3fe95c97ac583d91f6e7e3ab1ea66 Mon Sep 17 00:00:00 2001 From: Johnny Hujol Date: Wed, 5 Nov 2025 13:53:12 +0100 Subject: [PATCH 14/14] ref(logging): enhance logging for parameter processing Signed-off-by: Johnny Hujol --- install.sh | 42 +++++++++++++++++++ pom.xml | 2 +- .../ElasticsearchBackendClient.java | 9 +++- 3 files changed, 50 insertions(+), 3 deletions(-) create mode 100755 install.sh diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..308def6 --- /dev/null +++ b/install.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +if [[ $# -ne 1 ]]; then + echo "Usage: $0 " + exit 1 +fi + +declare BENCHMARK_PROJECT_DIR=$1 + + +declare VERSION=$(xmllint --xpath "/*[local-name()='project']/*[local-name()='version']/text()" ./pom.xml) +echo "Building JMeter Elasticsearch Backend Listener plugin version: $VERSION" +declare TEMP_LOGS="$(mktemp)" +mvn package >${TEMP_LOGS} 2>&1 +if [[ $? -ne 0 ]]; then + echo "Maven build failed. See details below:" + cat ${TEMP_LOGS} + rm ${TEMP_LOGS} + exit 1 +fi +rm ${TEMP_LOGS} + +echo "Copying JMeter Elasticsearch Backend Listener plugin to JMeter lib/ext directory and benchmark resources" +cp ./target/jmeter.backendlistener.elasticsearch-$VERSION.jar $HOME/.sdkman/candidates/jmeter/current/lib/ext +cp ./target/jmeter.backendlistener.elasticsearch-$VERSION.jar $BENCHMARK_PROJECT_DIR/benchmarks/tools/benchmark-deployment/src/main/resources + +echo Checking installation... +if [[ ! -f "$HOME/.sdkman/candidates/jmeter/current/lib/ext/jmeter.backendlistener.elasticsearch-$VERSION.jar" ]]; then + echo "Error: JMeter Elasticsearch Backend Listener plugin not found in JMeter lib/ext directory" + exit 1 +fi + +ls -l $HOME/.sdkman/candidates/jmeter/current/lib/ext/jmeter.backendlistener.elasticsearch-$VERSION.jar + +if [[ ! -f "$BENCHMARK_PROJECT_DIR/benchmarks/tools/benchmark-deployment/src/main/resources/jmeter.backendlistener.elasticsearch-$VERSION.jar" ]]; then + echo "Error: JMeter Elasticsearch Backend Listener plugin not found in benchmark resources" + exit 1 +fi + +ls -l $BENCHMARK_PROJECT_DIR/benchmarks/tools/benchmark-deployment/src/main/resources/jmeter.backendlistener.elasticsearch-$VERSION.jar + +echo "JMeter Elasticsearch Backend Listener plugin installed successfully in $HOME/.sdkman/candidates/jmeter/current/lib/ext and in $BENCHMARK_PROJECT_DIR/benchmarks/tools/benchmark-deployment/src/main/resources" \ No newline at end of file diff --git a/pom.xml b/pom.xml index 9bd4be7..db22c9e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 io.github.delirius325 jmeter.backendlistener.elasticsearch - 2.7.1-sirenbk.1 + 2.7.1 jar jmeter.backendlistener.elasticsearch diff --git a/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java b/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java index f25ead2..bb3142b 100644 --- a/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java +++ b/src/main/java/io/github/delirius325/jmeter/backendlistener/elasticsearch/ElasticsearchBackendClient.java @@ -21,6 +21,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashMap; @@ -258,6 +259,10 @@ public void handleSampleResults(List results, BackendListenerConte return; } + List paramNames = new ArrayList<>(); + context.getParameterNamesIterator().forEachRemaining(paramNames::add); + paramNames.stream().sorted().forEach(name -> logger.info("Parameter: {} = {}", name, context.getParameter(name))); + if (System.getProperty("SYS_DYNAMIC_ES_PER_SAMPLE_FILTER") != null) { // Override filters from ES_SAMPLE_FILTER if exists this.filters.clear(); @@ -303,9 +308,9 @@ public void handleSampleResults(List results, BackendListenerConte "The ElasticSearch Backend Listener was unable to add sampler to the list of samplers to send... More info in JMeter's console."); e.printStackTrace(); } - logger.info("Sampler result processed {} with filters {}", sr.getSampleLabel(), filtersAsString); + logger.info("Sampler result processed {} with filters {} (ES_SAMPLE_FILTER {})", sr.getSampleLabel(), filtersAsString, context.getParameter("es.sample.filter")); } else { - logger.info("Sampler result skipped {} with filters {}", sr.getSampleLabel(), filtersAsString); + logger.info("Sampler result skipped {} with filters {} (ES_SAMPLE_FILTER {})", sr.getSampleLabel(), filtersAsString, context.getParameter(ES_SAMPLE_FILTER)); } }