├── LICENSE.md ├── README.md └── apache-jmeter-listener-elasticsearch └── com └── doc └── jmeter └── listeners └── elasticsearch ├── ElasticsearchListener.java └── util ├── ElasticsearchListenerParameters.java └── ParameterValueChecker.java /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Vadim Klimov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!IMPORTANT] 2 | > This repository has been archived and is no longer maintained. 3 | 4 | # Elasticsearch listener for Apache JMeter 5 | The listener plugin for Apache JMeter that enables delivery of samples' result data to Elasticsearch server using Elasticsearch API. 6 | 7 | Usage of the plugin is described in SAP Community blog https://blogs.sap.com/2016/04/06/load-testing-with-jmeter-test-results-visualization-using-kibana-dashboards/. 8 | 9 | 10 | ## Prerequisites 11 | * Java runtime 1.8 or higher. 12 | 13 | 14 | ## Dependencies 15 | * Elasticsearch Java REST client library 16 | 17 | Documentation: https://www.elastic.co/guide/en/elasticsearch/client/java-rest/index.html 18 | 19 | Dependency declaration for Maven POM: 20 | 21 |
<dependency>
22 | 	<groupId>org.elasticsearch.client</groupId>
23 | 	<artifactId>rest</artifactId>
24 | 	<version>RELEASE</version>
25 | </dependency>
26 | * Several libraries (such as HTTP client that is used by Elasticsearch Java REST client, JSON processor, logging framework) that are already a part of JMeter distribution (the statement is based on content of distribution of JMeter version 3.2). 27 | 28 | 29 | ## Installation 30 | * Copy dependency libraries to the directory **/<JMeter home>/lib/**. 31 | * Copy plugin library to the directory **/<JMeter home>/lib/ext/**. 32 | 33 | 34 | ## Parameterization 35 | |Parameter|Description|Default value| 36 | |---|---|---| 37 | |elasticsearch.url|Elasticsearch server URL in format <scheme>://<host>:<port>|http://localhost:9200| 38 | |elasticsearch.index|Index to which documents containing sample results are to be added on Elasticsearch server|| 39 | |elasticsearch.type|Document type of documents containing sample results that are to be added on Elasticsearch server|| 40 | |elasticsearch.authenticationMethod|In case authentication is required at Elasticsearch server, authentication method to be used. If no authentication is required, the parameter value shall be left empty. Currently supported authentication methods: BASIC|Empty (no authentication)| 41 | |elasticsearch.user|User name for authentication of requests sent to Elasticsearch server. Applicable to basic authentication method|| 42 | |elasticsearch.password|Password for authentication of requests sent to Elasticsearch server. Applicable to basic authentication method|| 43 | |timezone.id|Timezone identifier|GMT| 44 | |result.attributes.excluded|Comma-separated names of sample result attributes that shall not be passed to Elasticsearch server. Valid attributes' names: Timestamp, StartTime, EndTime, Time, Latency, ConnectTime, IdleTime, SampleLabel, GroupName, ThreadName, ResponseCode, IsResponseCodeOk, IsSuccessful, SampleCount, ErrorCount, ContentType, MediaType, DataType, RequestHeaders, ResponseHeaders, HeadersSize, SamplerData, ResponseMessage, ResponseData, BodySize, Bytes|All attributes are passed| 45 | |proxy.url|In case connection to Elasticsearch server is over proxy server, proxy URL in format <scheme>://<host>:<port>|Empty (no proxy)| 46 | |proxy.authenticationMethod|In case authentication is required at proxy server, authentication method to be used. If no authentication is required, the parameter value shall be left empty. Currently supported authentication methods: BASIC, NTLM|Empty (no authentication)| 47 | |proxy.user|User name for authentication of requests sent over proxy server. Applicable to basic and NTLM authentication methods|| 48 | |proxy.password|Password for authentication of requests sent over proxy server. Applicable to basic and NTLM authentication methods|| 49 | |proxy.workstation|Workstation name for authentication of requests sent over proxy server. Applicable to NTLM authentication method. If no value is provided, local host name of the machine will be determined and used|| 50 | |proxy.domain|Domain name for authentication of requests sent over proxy server. Applicable to NTLM authentication method|| 51 | |experimental.connection.trustAllSslCertificates|Enable trust all SSL certificates when establishing connection to Elasticsearch server. Possible values: true / false. Not recommended for production usage due to security considerations|false| 52 | 53 | 54 | ## Additional notes 55 | The listener plugin has been tested with JMeter version 3.3 running on JRE versions 1.8 and 1.9 in conjunction with Elasticsearch server version 5.6.3. 56 | 57 | In recent versions of JMeter API, several methods related to sample result processing, have been deprecated and newer replacing methods were introduced. The current version of the listener plugin makes use of the latter methods. 58 | 59 | Recent versions of JMeter API deprecate usage of Jorphan Logging Manager, which is superseded with Simple Logging Facade for Java (SLF4J). The current version of the listener plugin makes use of SLF4J for logging. 60 | -------------------------------------------------------------------------------- /apache-jmeter-listener-elasticsearch/com/doc/jmeter/listeners/elasticsearch/ElasticsearchListener.java: -------------------------------------------------------------------------------- 1 | package com.doc.jmeter.listeners.elasticsearch; 2 | 3 | import java.io.IOException; 4 | import java.net.InetAddress; 5 | import java.net.MalformedURLException; 6 | import java.net.URL; 7 | import java.net.UnknownHostException; 8 | import java.security.KeyManagementException; 9 | import java.security.KeyStoreException; 10 | import java.security.NoSuchAlgorithmException; 11 | import java.time.Instant; 12 | import java.time.LocalDateTime; 13 | import java.time.ZoneId; 14 | import java.util.ArrayList; 15 | import java.util.Arrays; 16 | import java.util.Collections; 17 | import java.util.List; 18 | import java.util.UUID; 19 | import java.util.stream.Collectors; 20 | import java.util.stream.Stream; 21 | 22 | import javax.net.ssl.SSLContext; 23 | 24 | import org.apache.commons.lang3.exception.ExceptionUtils; 25 | import org.apache.http.HttpEntity; 26 | import org.apache.http.HttpHost; 27 | import org.apache.http.auth.AuthSchemeProvider; 28 | import org.apache.http.auth.AuthScope; 29 | import org.apache.http.auth.NTCredentials; 30 | import org.apache.http.auth.UsernamePasswordCredentials; 31 | import org.apache.http.client.CredentialsProvider; 32 | import org.apache.http.client.config.AuthSchemes; 33 | import org.apache.http.config.Lookup; 34 | import org.apache.http.config.RegistryBuilder; 35 | import org.apache.http.conn.ssl.NoopHostnameVerifier; 36 | import org.apache.http.conn.ssl.TrustSelfSignedStrategy; 37 | import org.apache.http.entity.ContentType; 38 | import org.apache.http.entity.StringEntity; 39 | import org.apache.http.impl.auth.BasicSchemeFactory; 40 | import org.apache.http.impl.auth.DigestSchemeFactory; 41 | import org.apache.http.impl.auth.KerberosSchemeFactory; 42 | import org.apache.http.impl.auth.NTLMSchemeFactory; 43 | import org.apache.http.impl.auth.SPNegoSchemeFactory; 44 | import org.apache.http.impl.client.BasicCredentialsProvider; 45 | import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; 46 | import org.apache.http.ssl.SSLContextBuilder; 47 | import org.apache.http.util.EntityUtils; 48 | import org.apache.jmeter.config.Arguments; 49 | import org.apache.jmeter.samplers.SampleResult; 50 | import org.apache.jmeter.visualizers.backend.AbstractBackendListenerClient; 51 | import org.apache.jmeter.visualizers.backend.BackendListenerContext; 52 | import org.elasticsearch.client.Response; 53 | import org.elasticsearch.client.RestClient; 54 | import org.elasticsearch.client.RestClientBuilder; 55 | import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback; 56 | import org.slf4j.Logger; 57 | import org.slf4j.LoggerFactory; 58 | 59 | import com.doc.jmeter.listeners.elasticsearch.util.ElasticsearchListenerParameters; 60 | import com.doc.jmeter.listeners.elasticsearch.util.ParameterValueChecker; 61 | 62 | import net.minidev.json.JSONObject; 63 | 64 | public class ElasticsearchListener extends AbstractBackendListenerClient { 65 | 66 | private enum SampleResultDefaultAttributes { 67 | Timestamp, StartTime, EndTime, Time, Latency, ConnectTime, IdleTime, SampleLabel, GroupName, ThreadName, ResponseCode, IsResponseCodeOk, IsSuccessful, SampleCount, ErrorCount, ContentType, MediaType, DataType, RequestHeaders, ResponseHeaders, HeadersSize, SamplerData, ResponseMessage, ResponseData, BodySize, Bytes 68 | }; 69 | 70 | private enum EsSupportedAuthMethods { 71 | BASIC 72 | }; 73 | 74 | private enum ProxySupportedAuthMethods { 75 | BASIC, NTLM 76 | }; 77 | 78 | private static final Logger LOGGER = LoggerFactory.getLogger(ElasticsearchListener.class); 79 | private static final Object LOCK = new Object(); 80 | 81 | private String esUrl; 82 | private String esProtocol; 83 | private String esHost; 84 | private int esPort; 85 | private String esIndex; 86 | private String esType; 87 | private boolean isEsAuth = false; 88 | private String esAuthMethod; 89 | private String esUser; 90 | private String esPassword; 91 | 92 | private String tzId; 93 | private String srExcludedAttributes; 94 | private List sampleResultExcludedAttributes = new ArrayList(); 95 | 96 | private boolean isProxyUse = false; 97 | private String proxyUrl; 98 | private String proxyProtocol; 99 | private String proxyHost; 100 | private int proxyPort; 101 | private boolean isProxyAuth = false; 102 | private String proxyAuthMethod; 103 | private String proxyUser; 104 | private String proxyPassword; 105 | private String proxyWorkstation; 106 | private String proxyDomain; 107 | 108 | private String xConnTrustAllSslCerts; 109 | 110 | private boolean isConnTrustAllSslCerts = false; 111 | 112 | private RestClient esClient = null; 113 | 114 | private boolean isError = false; 115 | 116 | /* 117 | * @Override public SampleResult createSampleResult(BackendListenerContext 118 | * context, SampleResult sampleResult) { 119 | * 120 | * // Not used in current implementation return null; 121 | * 122 | * } 123 | */ 124 | 125 | @Override 126 | public Arguments getDefaultParameters() { 127 | 128 | Arguments parameters = new Arguments(); 129 | 130 | parameters.addArgument(ElasticsearchListenerParameters.ELASTICSEARCH_URL, "http://localhost:9200"); 131 | parameters.addArgument(ElasticsearchListenerParameters.ELASTICSEARCH_INDEX, null); 132 | parameters.addArgument(ElasticsearchListenerParameters.ELASTICSEARCH_TYPE, null); 133 | parameters.addArgument(ElasticsearchListenerParameters.ELASTICSEARCH_AUTH_METHOD, null); 134 | parameters.addArgument(ElasticsearchListenerParameters.ELASTICSEARCH_USER, null); 135 | parameters.addArgument(ElasticsearchListenerParameters.ELASTICSEARCH_PASSWORD, null); 136 | parameters.addArgument(ElasticsearchListenerParameters.TIMEZONE_ID, "GMT"); 137 | parameters.addArgument(ElasticsearchListenerParameters.RESULT_EXCLUDED_ATTRIBUTES, null); 138 | parameters.addArgument(ElasticsearchListenerParameters.PROXY_URL, null); 139 | parameters.addArgument(ElasticsearchListenerParameters.PROXY_AUTH_METHOD, null); 140 | parameters.addArgument(ElasticsearchListenerParameters.PROXY_USER, null); 141 | parameters.addArgument(ElasticsearchListenerParameters.PROXY_PASSWORD, null); 142 | parameters.addArgument(ElasticsearchListenerParameters.PROXY_WORKSTATION, null); 143 | parameters.addArgument(ElasticsearchListenerParameters.PROXY_DOMAIN, null); 144 | parameters.addArgument(ElasticsearchListenerParameters.X_CONN_TRUST_ALL_CERTS, "false"); 145 | 146 | return parameters; 147 | 148 | } 149 | 150 | @Override 151 | public void handleSampleResults(List sampleResults, BackendListenerContext context) { 152 | 153 | if (isError) { 154 | LOGGER.warn("Sample result will not be sent to Elasticsearch server due to server ping failure"); 155 | return; 156 | } 157 | 158 | LOGGER.debug("Processing sample results for request to Elasticsearch server"); 159 | 160 | synchronized (LOCK) { 161 | 162 | sampleResults.forEach(sampleResult -> { 163 | 164 | LOGGER.debug("Preparing sample result to send to Elasticsearch server"); 165 | String sampleResult4External = getSampleResult4External(sampleResult); 166 | 167 | if (sampleResult4External == null || sampleResult4External.isEmpty()) { 168 | LOGGER.warn( 169 | "Sample result will not be sent to Elasticsearch server because message content is missing"); 170 | return; 171 | } 172 | HttpEntity sampleResultEntity = new StringEntity(sampleResult4External, ContentType.APPLICATION_JSON); 173 | 174 | String sampleResultEsDocumentLocation = "/" + esIndex + "/" + esType + "/" + UUID .randomUUID() 175 | .toString(); 176 | 177 | LOGGER.debug("Elasticsearch request - document location: " + sampleResultEsDocumentLocation); 178 | LOGGER.debug("Elasticsearch request - document data:\n" + sampleResult4External); 179 | 180 | try { 181 | Response sampleResultEsDocumentResponse = esClient.performRequest("POST", 182 | sampleResultEsDocumentLocation, Collections.emptyMap(), sampleResultEntity); 183 | 184 | LOGGER.debug("Elasticsearch server response - HTTP status code: " 185 | + sampleResultEsDocumentResponse.getStatusLine() 186 | .getStatusCode()); 187 | LOGGER.debug("Elasticsearch server response - HTTP body:\n" 188 | + EntityUtils.toString(sampleResultEsDocumentResponse.getEntity())); 189 | } catch (IOException e) { 190 | LOGGER.error("Sending sample result to Elasticsearch server failed"); 191 | LOGGER.error(ExceptionUtils.getStackTrace(e)); 192 | } 193 | 194 | }); 195 | } 196 | 197 | } 198 | 199 | @Override 200 | public void setupTest(BackendListenerContext context) throws Exception { 201 | 202 | LOGGER.debug("Initializing Elasticsearch listener"); 203 | 204 | getListenerParameters(context); 205 | checkListenerParameters(); 206 | 207 | if (isError) { 208 | LOGGER.error( 209 | "One or several checks of Elasticsearch listener parameters failed. Terminating Elasticsearch listener"); 210 | return; 211 | } 212 | 213 | esClient = getElasticsearchRestClient(); 214 | 215 | if (isError) { 216 | LOGGER.error("Elasticsearch REST client initialization failed. Terminating Elasticsearch listener"); 217 | return; 218 | } 219 | 220 | try { 221 | esClient.performRequest("HEAD", "/", Collections.emptyMap()); 222 | isError = false; 223 | LOGGER.info("Elasticsearch server ping test: Successful"); 224 | } catch (IOException e) { 225 | isError = true; 226 | LOGGER.error("Elasticsearch server ping test: Failed"); 227 | LOGGER.error(ExceptionUtils.getStackTrace(e)); 228 | } 229 | 230 | } 231 | 232 | @Override 233 | public void teardownTest(BackendListenerContext context) throws Exception { 234 | 235 | LOGGER.debug("Shutting down Elasticsearch listener"); 236 | 237 | if (esClient != null) { 238 | try { 239 | esClient.close(); 240 | } catch (IOException e) { 241 | LOGGER.error("Connection closure to Elasticsearch server failed"); 242 | LOGGER.error(ExceptionUtils.getStackTrace(e)); 243 | } 244 | } 245 | 246 | } 247 | 248 | private void getListenerParameters(BackendListenerContext context) { 249 | 250 | LOGGER.debug("Retrieving Elasticsearch listener parameters"); 251 | 252 | esUrl = context .getParameter(ElasticsearchListenerParameters.ELASTICSEARCH_URL) 253 | .trim(); 254 | esIndex = context .getParameter(ElasticsearchListenerParameters.ELASTICSEARCH_INDEX) 255 | .trim() 256 | .toLowerCase(); 257 | esType = context.getParameter(ElasticsearchListenerParameters.ELASTICSEARCH_TYPE) 258 | .trim(); 259 | esAuthMethod = context .getParameter(ElasticsearchListenerParameters.ELASTICSEARCH_AUTH_METHOD) 260 | .trim() 261 | .toUpperCase(); 262 | esUser = context.getParameter(ElasticsearchListenerParameters.ELASTICSEARCH_USER) 263 | .trim(); 264 | esPassword = context.getParameter(ElasticsearchListenerParameters.ELASTICSEARCH_PASSWORD) 265 | .trim(); 266 | 267 | tzId = context .getParameter(ElasticsearchListenerParameters.TIMEZONE_ID) 268 | .trim() 269 | .toUpperCase(); 270 | srExcludedAttributes = context .getParameter(ElasticsearchListenerParameters.RESULT_EXCLUDED_ATTRIBUTES) 271 | .trim(); 272 | proxyUrl = context .getParameter(ElasticsearchListenerParameters.PROXY_URL) 273 | .trim(); 274 | proxyAuthMethod = context .getParameter(ElasticsearchListenerParameters.PROXY_AUTH_METHOD) 275 | .trim() 276 | .toUpperCase(); 277 | proxyUser = context .getParameter(ElasticsearchListenerParameters.PROXY_USER) 278 | .trim(); 279 | proxyPassword = context .getParameter(ElasticsearchListenerParameters.PROXY_PASSWORD) 280 | .trim(); 281 | proxyWorkstation = context .getParameter(ElasticsearchListenerParameters.PROXY_WORKSTATION) 282 | .trim(); 283 | proxyDomain = context .getParameter(ElasticsearchListenerParameters.PROXY_DOMAIN) 284 | .trim(); 285 | xConnTrustAllSslCerts = context .getParameter(ElasticsearchListenerParameters.X_CONN_TRUST_ALL_CERTS) 286 | .trim() 287 | .toLowerCase(); 288 | 289 | } 290 | 291 | private void checkListenerParameters() { 292 | 293 | LOGGER.debug("Checking Elasticsearch listener parameters"); 294 | 295 | // Get list of excluded attributes of sample result 296 | if (!ParameterValueChecker.isNullOrEmpty(srExcludedAttributes)) { 297 | List sampleResultDefaultAttributes = Stream .of(SampleResultDefaultAttributes.values()) 298 | .map(SampleResultDefaultAttributes::name) 299 | .collect(Collectors.toList()); 300 | sampleResultExcludedAttributes.addAll(Arrays.asList(srExcludedAttributes.split(","))); 301 | sampleResultExcludedAttributes.retainAll(sampleResultDefaultAttributes); 302 | LOGGER.debug("Possible sample result attributes: " + sampleResultDefaultAttributes); 303 | LOGGER.debug("Excluded sample result attributes: " + sampleResultExcludedAttributes); 304 | } 305 | 306 | // Check Elasticsearch server URL 307 | if (!ParameterValueChecker.isNullOrEmpty(esUrl)) { 308 | try { 309 | URL esURL = new URL(esUrl); 310 | esProtocol = esURL.getProtocol(); 311 | esHost = esURL.getHost(); 312 | esPort = esURL.getPort(); 313 | } catch (MalformedURLException e) { 314 | isError = true; 315 | LOGGER.error("Parsing Elasticsearch server URL failed"); 316 | LOGGER.error(ExceptionUtils.getStackTrace(e)); 317 | } 318 | } else { 319 | isError = true; 320 | LOGGER.error("Mandatory parameter missing. Check parameter: " 321 | + ElasticsearchListenerParameters.ELASTICSEARCH_URL); 322 | } 323 | 324 | // Check Elasticsearch index and type 325 | if (ParameterValueChecker.isNullOrEmpty(esIndex)) { 326 | isError = true; 327 | LOGGER.error("Mandatory parameter missing. Check parameter: " 328 | + ElasticsearchListenerParameters.ELASTICSEARCH_INDEX); 329 | } 330 | 331 | if (ParameterValueChecker.isNullOrEmpty(esType)) { 332 | isError = true; 333 | LOGGER.error("Mandatory parameter missing. Check parameter: " 334 | + ElasticsearchListenerParameters.ELASTICSEARCH_TYPE); 335 | } 336 | 337 | // Check Elasticsearch authentication method 338 | if (!ParameterValueChecker.isNullOrEmpty(esAuthMethod)) { 339 | isEsAuth = true; 340 | 341 | try { 342 | switch (EsSupportedAuthMethods.valueOf(esAuthMethod)) { 343 | 344 | case BASIC: 345 | if (ParameterValueChecker.isNullOrEmpty(esUser) 346 | || ParameterValueChecker.isNullOrEmpty(esPassword)) { 347 | isError = true; 348 | LOGGER.error( 349 | "Elasticsearch basic authentication method selected. One or several mandatory parameters missing. Check parameters: " 350 | + ElasticsearchListenerParameters.ELASTICSEARCH_USER + ", " 351 | + ElasticsearchListenerParameters.ELASTICSEARCH_PASSWORD); 352 | } 353 | 354 | break; 355 | 356 | } 357 | } catch (IllegalArgumentException e) { 358 | isError = true; 359 | LOGGER.error("Unsupported Elasticsearch authentication method. Check parameter: " 360 | + ElasticsearchListenerParameters.ELASTICSEARCH_AUTH_METHOD); 361 | } 362 | 363 | } 364 | 365 | // Check proxy URL 366 | if (!ParameterValueChecker.isNullOrEmpty(proxyUrl)) { 367 | isProxyUse = true; 368 | 369 | try { 370 | URL proxyURL = new URL(proxyUrl); 371 | proxyProtocol = proxyURL.getProtocol(); 372 | proxyHost = proxyURL.getHost(); 373 | proxyPort = proxyURL.getPort(); 374 | } catch (MalformedURLException e) { 375 | isError = true; 376 | LOGGER.error("Parsing proxy URL failed"); 377 | LOGGER.error(ExceptionUtils.getStackTrace(e)); 378 | } 379 | } 380 | 381 | // Check proxy authentication method 382 | if (isProxyUse && !ParameterValueChecker.isNullOrEmpty(proxyAuthMethod)) { 383 | isProxyAuth = true; 384 | 385 | try { 386 | switch (ProxySupportedAuthMethods.valueOf(proxyAuthMethod)) { 387 | 388 | case BASIC: 389 | if (ParameterValueChecker.isNullOrEmpty(proxyUser) 390 | || ParameterValueChecker.isNullOrEmpty(proxyPassword)) { 391 | isError = true; 392 | LOGGER.error( 393 | "Proxy basic authentication method selected. One or several mandatory parameters missing. Check parameters: " 394 | + ElasticsearchListenerParameters.PROXY_USER + ", " 395 | + ElasticsearchListenerParameters.PROXY_PASSWORD); 396 | } 397 | 398 | break; 399 | 400 | case NTLM: 401 | if (ParameterValueChecker.isNullOrEmpty(proxyUser) 402 | || ParameterValueChecker.isNullOrEmpty(proxyPassword) 403 | || ParameterValueChecker.isNullOrEmpty(proxyDomain)) { 404 | isError = true; 405 | LOGGER.error( 406 | "Proxy NTLM authentication method selected. One or several mandatory parameters missing. Check parameters: " 407 | + ElasticsearchListenerParameters.PROXY_USER + ", " 408 | + ElasticsearchListenerParameters.PROXY_PASSWORD + ", " 409 | + ElasticsearchListenerParameters.PROXY_DOMAIN); 410 | } else { 411 | if (ParameterValueChecker.isNullOrEmpty(proxyWorkstation)) { 412 | LOGGER.debug( 413 | "Proxy NTLM authentication method selected. Workstation not specified. Attempting to determine local host name"); 414 | 415 | try { 416 | proxyWorkstation = InetAddress .getLocalHost() 417 | .getHostName(); 418 | LOGGER.info("Local host name: " + proxyWorkstation); 419 | } catch (UnknownHostException e) { 420 | isError = true; 421 | LOGGER.error("Determination of local host name failed"); 422 | LOGGER.error(ExceptionUtils.getStackTrace(e)); 423 | } 424 | } 425 | } 426 | 427 | break; 428 | 429 | } 430 | } catch (IllegalArgumentException e) { 431 | isError = true; 432 | LOGGER.error("Unsupported proxy authentication method. Check parameter: " 433 | + ElasticsearchListenerParameters.PROXY_AUTH_METHOD); 434 | } 435 | 436 | } 437 | 438 | // Check timezone ID 439 | if (!ZoneId .getAvailableZoneIds() 440 | .contains(tzId)) { 441 | LOGGER.warn("Incorrect timezone ID. Falling back to timezone ID 'GMT'. Check parameter: " 442 | + ElasticsearchListenerParameters.TIMEZONE_ID); 443 | tzId = "GMT"; 444 | } 445 | 446 | // Check trust all SSL certificates 447 | isConnTrustAllSslCerts = ParameterValueChecker.convertBoolean( 448 | ParameterValueChecker.isTrueOrFalse(xConnTrustAllSslCerts), false); 449 | 450 | } 451 | 452 | private RestClient getElasticsearchRestClient() { 453 | 454 | if (isError) { 455 | LOGGER.warn( 456 | "Elasticsearch REST client will not be initialized due to failure during checks of Elasticsearch listener parameters. Terminating Elasticsearch listener"); 457 | return null; 458 | } 459 | 460 | LOGGER.debug("Initializing Elasticsearch REST client"); 461 | 462 | RestClientBuilder clientBuilder = RestClient.builder(new HttpHost(esHost, esPort, esProtocol)); 463 | 464 | HttpClientConfigCallback httpClientConfig = new RestClientBuilder.HttpClientConfigCallback() { 465 | 466 | @Override 467 | public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) { 468 | 469 | if (isEsAuth || isProxyAuth) { 470 | Lookup authSchemeRegistry = RegistryBuilder .create() 471 | .register(AuthSchemes.BASIC, 472 | new BasicSchemeFactory()) 473 | .register(AuthSchemes.DIGEST, 474 | new DigestSchemeFactory()) 475 | .register(AuthSchemes.NTLM, 476 | new NTLMSchemeFactory()) 477 | .register(AuthSchemes.SPNEGO, 478 | new SPNegoSchemeFactory()) 479 | .register(AuthSchemes.KERBEROS, 480 | new KerberosSchemeFactory()) 481 | .build(); 482 | 483 | httpClientBuilder.setDefaultAuthSchemeRegistry(authSchemeRegistry); 484 | 485 | CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); 486 | 487 | // Elasticsearch authentication 488 | if (isEsAuth) { 489 | switch (EsSupportedAuthMethods.valueOf(esAuthMethod)) { 490 | 491 | case BASIC: 492 | credentialsProvider.setCredentials(new AuthScope(esHost, esPort), 493 | new UsernamePasswordCredentials(esUser, esPassword)); 494 | break; 495 | 496 | } 497 | } 498 | 499 | // Proxy authentication 500 | if (isProxyUse && isProxyAuth) { 501 | switch (ProxySupportedAuthMethods.valueOf(proxyAuthMethod)) { 502 | 503 | case BASIC: 504 | credentialsProvider.setCredentials(new AuthScope(proxyHost, proxyPort), 505 | new UsernamePasswordCredentials(proxyUser, proxyPassword)); 506 | break; 507 | 508 | case NTLM: 509 | credentialsProvider.setCredentials(new AuthScope(proxyHost, proxyPort), 510 | new NTCredentials(proxyUser, proxyPassword, proxyWorkstation, proxyDomain)); 511 | break; 512 | 513 | } 514 | } 515 | 516 | httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider); 517 | 518 | } 519 | 520 | // Use proxy 521 | if (isProxyUse) { 522 | httpClientBuilder.setProxy(new HttpHost(proxyHost, proxyPort, proxyProtocol)); 523 | } 524 | 525 | // Trust all SSL certificates (experimental feature) 526 | if (isConnTrustAllSslCerts) { 527 | try { 528 | SSLContext sslContext = SSLContextBuilder .create() 529 | .loadTrustMaterial(new TrustSelfSignedStrategy()) 530 | .build(); 531 | httpClientBuilder .setSSLContext(sslContext) 532 | .setSSLHostnameVerifier(new NoopHostnameVerifier()); 533 | } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) { 534 | LOGGER.warn("Enablement of trust all SSL certificates failed"); 535 | LOGGER.warn(ExceptionUtils.getStackTrace(e)); 536 | } 537 | } 538 | 539 | return httpClientBuilder; 540 | } 541 | 542 | }; 543 | 544 | clientBuilder.setHttpClientConfigCallback(httpClientConfig); 545 | 546 | esClient = clientBuilder.build(); 547 | 548 | return esClient; 549 | 550 | } 551 | 552 | private String getSampleResult4External(SampleResult sampleResult) { 553 | 554 | LOGGER.debug("Preparing sample result message"); 555 | 556 | JSONObject sampleResult4ExternalJson = new JSONObject(); 557 | 558 | Stream .of(SampleResultDefaultAttributes.values()) 559 | .forEach(attribute -> { 560 | 561 | if (!sampleResultExcludedAttributes.contains(attribute.toString())) { 562 | switch (attribute) { 563 | case Timestamp: 564 | sampleResult4ExternalJson.put( 565 | attribute.toString(), LocalDateTime 566 | .ofInstant( 567 | Instant.ofEpochMilli( 568 | sampleResult.getTimeStamp()), 569 | ZoneId.of(tzId)) 570 | .toString()); 571 | break; 572 | case StartTime: 573 | sampleResult4ExternalJson.put( 574 | attribute.toString(), LocalDateTime 575 | .ofInstant( 576 | Instant.ofEpochMilli( 577 | sampleResult.getStartTime()), 578 | ZoneId.of(tzId)) 579 | .toString()); 580 | break; 581 | case EndTime: 582 | sampleResult4ExternalJson.put( 583 | attribute.toString(), LocalDateTime 584 | .ofInstant( 585 | Instant.ofEpochMilli( 586 | sampleResult.getEndTime()), 587 | ZoneId.of(tzId)) 588 | .toString()); 589 | break; 590 | case Time: 591 | sampleResult4ExternalJson.put(attribute.toString(), sampleResult.getTime()); 592 | break; 593 | case Latency: 594 | sampleResult4ExternalJson.put(attribute.toString(), sampleResult.getLatency()); 595 | break; 596 | case ConnectTime: 597 | sampleResult4ExternalJson.put(attribute.toString(), sampleResult.getConnectTime()); 598 | break; 599 | case IdleTime: 600 | sampleResult4ExternalJson.put(attribute.toString(), sampleResult.getIdleTime()); 601 | break; 602 | case SampleLabel: 603 | sampleResult4ExternalJson.put(attribute.toString(), sampleResult.getSampleLabel()); 604 | break; 605 | case GroupName: 606 | sampleResult4ExternalJson.put(attribute.toString(), sampleResult.getSampleLabel(true) 607 | .substring(0, 608 | sampleResult.getSampleLabel( 609 | true) 610 | .indexOf( 611 | sampleResult.getSampleLabel()) 612 | - 1)); 613 | break; 614 | case ThreadName: 615 | sampleResult4ExternalJson.put(attribute.toString(), sampleResult.getThreadName()); 616 | break; 617 | case ResponseCode: 618 | sampleResult4ExternalJson.put(attribute.toString(), sampleResult.getResponseCode()); 619 | break; 620 | case IsResponseCodeOk: 621 | sampleResult4ExternalJson.put(attribute.toString(), sampleResult.isResponseCodeOK()); 622 | break; 623 | case IsSuccessful: 624 | sampleResult4ExternalJson.put(attribute.toString(), sampleResult.isSuccessful()); 625 | break; 626 | case SampleCount: 627 | sampleResult4ExternalJson.put(attribute.toString(), sampleResult.getSampleCount()); 628 | break; 629 | case ErrorCount: 630 | sampleResult4ExternalJson.put(attribute.toString(), sampleResult.getErrorCount()); 631 | break; 632 | case ContentType: 633 | sampleResult4ExternalJson.put(attribute.toString(), sampleResult.getContentType()); 634 | break; 635 | case MediaType: 636 | sampleResult4ExternalJson.put(attribute.toString(), sampleResult.getMediaType()); 637 | break; 638 | case DataType: 639 | sampleResult4ExternalJson.put(attribute.toString(), sampleResult.getDataType()); 640 | break; 641 | case RequestHeaders: 642 | sampleResult4ExternalJson.put(attribute.toString(), sampleResult.getRequestHeaders()); 643 | break; 644 | case ResponseHeaders: 645 | sampleResult4ExternalJson.put(attribute.toString(), sampleResult.getResponseHeaders()); 646 | break; 647 | case HeadersSize: 648 | sampleResult4ExternalJson.put(attribute.toString(), sampleResult.getHeadersSize()); 649 | break; 650 | case SamplerData: 651 | sampleResult4ExternalJson.put(attribute.toString(), sampleResult.getSamplerData()); 652 | break; 653 | case ResponseMessage: 654 | sampleResult4ExternalJson.put(attribute.toString(), sampleResult.getResponseMessage()); 655 | break; 656 | case ResponseData: 657 | sampleResult4ExternalJson.put(attribute.toString(), sampleResult.getResponseDataAsString()); 658 | break; 659 | case BodySize: 660 | sampleResult4ExternalJson.put(attribute.toString(), sampleResult.getBodySizeAsLong()); 661 | break; 662 | case Bytes: 663 | sampleResult4ExternalJson.put(attribute.toString(), sampleResult.getBytesAsLong()); 664 | break; 665 | } 666 | } 667 | 668 | }); 669 | 670 | if (sampleResult4ExternalJson != null && !sampleResult4ExternalJson.isEmpty()) { 671 | return sampleResult4ExternalJson.toString(); 672 | } else { 673 | return null; 674 | } 675 | 676 | } 677 | 678 | } -------------------------------------------------------------------------------- /apache-jmeter-listener-elasticsearch/com/doc/jmeter/listeners/elasticsearch/util/ElasticsearchListenerParameters.java: -------------------------------------------------------------------------------- 1 | package com.doc.jmeter.listeners.elasticsearch.util; 2 | 3 | public class ElasticsearchListenerParameters { 4 | 5 | public static final String ELASTICSEARCH_URL = "elasticsearch.url"; 6 | public static final String ELASTICSEARCH_INDEX = "elasticsearch.index"; 7 | public static final String ELASTICSEARCH_TYPE = "elasticsearch.type"; 8 | public static final String ELASTICSEARCH_AUTH_METHOD = "elasticsearch.authenticationMethod"; 9 | public static final String ELASTICSEARCH_USER = "elasticsearch.user"; 10 | public static final String ELASTICSEARCH_PASSWORD = "elasticsearch.password"; 11 | public static final String TIMEZONE_ID = "timezone.id"; 12 | public static final String RESULT_EXCLUDED_ATTRIBUTES = "result.attributes.excluded"; 13 | public static final String PROXY_URL = "proxy.url"; 14 | public static final String PROXY_AUTH_METHOD = "proxy.authenticationMethod"; 15 | public static final String PROXY_USER = "proxy.user"; 16 | public static final String PROXY_PASSWORD = "proxy.password"; 17 | public static final String PROXY_WORKSTATION = "proxy.workstation"; 18 | public static final String PROXY_DOMAIN = "proxy.domain"; 19 | public static final String X_CONN_TRUST_ALL_CERTS = "experimental.connection.trustAllSslCertificates"; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /apache-jmeter-listener-elasticsearch/com/doc/jmeter/listeners/elasticsearch/util/ParameterValueChecker.java: -------------------------------------------------------------------------------- 1 | package com.doc.jmeter.listeners.elasticsearch.util; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | public class ParameterValueChecker { 8 | 9 | private static final List VALUES_TRUE = new ArrayList( 10 | Arrays.asList(new String[] { "1", "true", "yes" })); 11 | private static final List VALUES_FALSE = new ArrayList( 12 | Arrays.asList(new String[] { "0", "false", "no" })); 13 | 14 | public static boolean isNullOrEmpty(String value) { 15 | return (value == null || value.isEmpty()) ? true : false; 16 | } 17 | 18 | public static Boolean isTrueOrFalse(String value) { 19 | if (!isNullOrEmpty(value)) { 20 | if (VALUES_TRUE.contains(String .valueOf(value) 21 | .trim() 22 | .toLowerCase())) { 23 | return Boolean.TRUE; 24 | } else if (VALUES_FALSE.contains(String .valueOf(value) 25 | .trim() 26 | .toLowerCase())) { 27 | return Boolean.FALSE; 28 | } else { 29 | return null; 30 | } 31 | } else { 32 | return null; 33 | } 34 | } 35 | 36 | public static boolean convertBoolean(Boolean convertedValue, boolean defaultValue) { 37 | return (convertedValue != null) ? convertedValue : defaultValue; 38 | } 39 | 40 | } 41 | --------------------------------------------------------------------------------