You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jmeter.apache.org by fs...@apache.org on 2021/03/12 16:20:56 UTC
[jmeter] 02/12: Added transaction timeout option
This is an automated email from the ASF dual-hosted git repository.
fschumacher pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/jmeter.git
commit 0ce5d27e4a904419edc9670b40b5fb0aaafaf411
Author: David Pecollet <da...@gmail.com>
AuthorDate: Mon Dec 14 17:48:32 2020 +0000
Added transaction timeout option
- new tx timeout option
- fixed results consumption issue in 4.x
- separated options from query in UI + better descriptions
---
bin/examples/Bolt Request.jmx | 121 +++++++++++++++++++++
.../bolt/sampler/AbstractBoltTestElement.java | 41 ++++++-
.../jmeter/protocol/bolt/sampler/BoltSampler.java | 34 ++++--
.../sampler/BoltTestElementBeanInfoSupport.java | 17 ++-
.../bolt/sampler/BoltSamplerResources.properties | 7 +-
.../protocol/bolt/sampler/BoltSamplerSpec.groovy | 11 +-
6 files changed, 208 insertions(+), 23 deletions(-)
diff --git a/bin/examples/Bolt Request.jmx b/bin/examples/Bolt Request.jmx
new file mode 100644
index 0000000..fad690b
--- /dev/null
+++ b/bin/examples/Bolt Request.jmx
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.4.1-SNAPSHOT ec1b44c">
+ <hashTree>
+ <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
+ <stringProp name="TestPlan.comments"></stringProp>
+ <boolProp name="TestPlan.functional_mode">false</boolProp>
+ <boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
+ <boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
+ <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+ <collectionProp name="Arguments.arguments"/>
+ </elementProp>
+ <stringProp name="TestPlan.user_define_classpath"></stringProp>
+ </TestPlan>
+ <hashTree>
+ <BoltConnectionElement guiclass="TestBeanGUI" testclass="BoltConnectionElement" testname="Bolt Connection Configuration" enabled="true">
+ <stringProp name="boltUri">neo4j://localhost:7617</stringProp>
+ <stringProp name="password">changeme</stringProp>
+ <stringProp name="username">neo4j</stringProp>
+ </BoltConnectionElement>
+ <hashTree/>
+ <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true">
+ <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
+ <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
+ <boolProp name="LoopController.continue_forever">false</boolProp>
+ <stringProp name="LoopController.loops">10</stringProp>
+ </elementProp>
+ <stringProp name="ThreadGroup.num_threads">1</stringProp>
+ <stringProp name="ThreadGroup.ramp_time">1</stringProp>
+ <boolProp name="ThreadGroup.scheduler">false</boolProp>
+ <stringProp name="ThreadGroup.duration"></stringProp>
+ <stringProp name="ThreadGroup.delay"></stringProp>
+ <boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
+ </ThreadGroup>
+ <hashTree>
+ <ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true">
+ <boolProp name="ResultCollector.error_logging">false</boolProp>
+ <objProp>
+ <name>saveConfig</name>
+ <value class="SampleSaveConfiguration">
+ <time>true</time>
+ <latency>true</latency>
+ <timestamp>true</timestamp>
+ <success>true</success>
+ <label>true</label>
+ <code>true</code>
+ <message>true</message>
+ <threadName>true</threadName>
+ <dataType>true</dataType>
+ <encoding>false</encoding>
+ <assertions>true</assertions>
+ <subresults>true</subresults>
+ <responseData>false</responseData>
+ <samplerData>false</samplerData>
+ <xml>false</xml>
+ <fieldNames>true</fieldNames>
+ <responseHeaders>false</responseHeaders>
+ <requestHeaders>false</requestHeaders>
+ <responseDataOnError>false</responseDataOnError>
+ <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
+ <assertionsResultsToSave>0</assertionsResultsToSave>
+ <bytes>true</bytes>
+ <sentBytes>true</sentBytes>
+ <url>true</url>
+ <threadCounts>true</threadCounts>
+ <idleTime>true</idleTime>
+ <connectTime>true</connectTime>
+ </value>
+ </objProp>
+ <stringProp name="filename"></stringProp>
+ </ResultCollector>
+ <hashTree/>
+ <BoltSampler guiclass="TestBeanGUI" testclass="BoltSampler" testname="Bolt Request" enabled="true">
+ <stringProp name="cypher">call dbms.cluster.role("owowo")</stringProp>
+ <stringProp name="params">{"paramName":"paramValue"}</stringProp>
+ <boolProp name="recordQueryResults">true</boolProp>
+ <stringProp name="accessMode">WRITE</stringProp>
+ <intProp name="txTimeout">10</intProp>
+ <stringProp name="database">owowo</stringProp>
+ </BoltSampler>
+ <hashTree/>
+ </hashTree>
+ <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true">
+ <boolProp name="ResultCollector.error_logging">false</boolProp>
+ <objProp>
+ <name>saveConfig</name>
+ <value class="SampleSaveConfiguration">
+ <time>true</time>
+ <latency>true</latency>
+ <timestamp>true</timestamp>
+ <success>true</success>
+ <label>true</label>
+ <code>true</code>
+ <message>true</message>
+ <threadName>true</threadName>
+ <dataType>true</dataType>
+ <encoding>false</encoding>
+ <assertions>true</assertions>
+ <subresults>true</subresults>
+ <responseData>false</responseData>
+ <samplerData>false</samplerData>
+ <xml>false</xml>
+ <fieldNames>true</fieldNames>
+ <responseHeaders>false</responseHeaders>
+ <requestHeaders>false</requestHeaders>
+ <responseDataOnError>false</responseDataOnError>
+ <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
+ <assertionsResultsToSave>0</assertionsResultsToSave>
+ <bytes>true</bytes>
+ <sentBytes>true</sentBytes>
+ <url>true</url>
+ <threadCounts>true</threadCounts>
+ <idleTime>true</idleTime>
+ <connectTime>true</connectTime>
+ </value>
+ </objProp>
+ <stringProp name="filename"></stringProp>
+ </ResultCollector>
+ <hashTree/>
+ </hashTree>
+ </hashTree>
+</jmeterTestPlan>
diff --git a/src/protocol/bolt/src/main/java/org/apache/jmeter/protocol/bolt/sampler/AbstractBoltTestElement.java b/src/protocol/bolt/src/main/java/org/apache/jmeter/protocol/bolt/sampler/AbstractBoltTestElement.java
index bba0c83..b30f750 100644
--- a/src/protocol/bolt/src/main/java/org/apache/jmeter/protocol/bolt/sampler/AbstractBoltTestElement.java
+++ b/src/protocol/bolt/src/main/java/org/apache/jmeter/protocol/bolt/sampler/AbstractBoltTestElement.java
@@ -17,26 +17,39 @@
package org.apache.jmeter.protocol.bolt.sampler;
+import java.time.Duration;
+
import org.apache.jmeter.testelement.AbstractTestElement;
import org.neo4j.driver.AccessMode;
+import org.neo4j.driver.SessionConfig;
+import org.neo4j.driver.TransactionConfig;
public abstract class AbstractBoltTestElement extends AbstractTestElement {
private String cypher;
private String params;
private String database;
- private AccessMode accessMode;
+ private String accessMode;
private boolean recordQueryResults;
+ private int txTimeout;
+
+ public int getTxTimeout() {
+ return txTimeout;
+ }
- public AccessMode getAccessMode() {
+ public void setTxTimeout(int txTimeout) {
+ this.txTimeout = txTimeout;
+ }
+
+ public String getAccessMode() {
if (accessMode != null) {
return accessMode;
} else {
- return AccessMode.WRITE;
+ return "WRITE";
}
}
- public void setAccessMode(AccessMode accessMode) {
+ public void setAccessMode(String accessMode) {
this.accessMode = accessMode;
}
@@ -71,4 +84,24 @@ public abstract class AbstractBoltTestElement extends AbstractTestElement {
public void setRecordQueryResults(boolean recordQueryResults) {
this.recordQueryResults = recordQueryResults;
}
+
+ public SessionConfig getSessionConfig() {
+ SessionConfig.Builder sessionConfigBuilder = SessionConfig.builder()
+ .withDefaultAccessMode(Enum.valueOf(AccessMode.class, getAccessMode()));
+
+ if (database != null && !"".equals(database)) {
+ sessionConfigBuilder.withDatabase(database);
+ }
+
+ return sessionConfigBuilder.build();
+ }
+ public TransactionConfig getTransactionConfig() {
+ TransactionConfig.Builder txConfigBuilder = TransactionConfig.builder();
+
+ if (txTimeout > 0) {
+ txConfigBuilder.withTimeout(Duration.ofSeconds(txTimeout));
+ }
+
+ return txConfigBuilder.build();
+ }
}
diff --git a/src/protocol/bolt/src/main/java/org/apache/jmeter/protocol/bolt/sampler/BoltSampler.java b/src/protocol/bolt/src/main/java/org/apache/jmeter/protocol/bolt/sampler/BoltSampler.java
index dfc5d47..e0fa5e3 100644
--- a/src/protocol/bolt/src/main/java/org/apache/jmeter/protocol/bolt/sampler/BoltSampler.java
+++ b/src/protocol/bolt/src/main/java/org/apache/jmeter/protocol/bolt/sampler/BoltSampler.java
@@ -22,6 +22,7 @@ import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -35,12 +36,12 @@ import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.samplers.Sampler;
import org.apache.jmeter.testbeans.TestBean;
import org.apache.jmeter.testelement.TestElement;
-import org.neo4j.driver.AccessMode;
import org.neo4j.driver.Driver;
import org.neo4j.driver.Record;
import org.neo4j.driver.Result;
import org.neo4j.driver.Session;
import org.neo4j.driver.SessionConfig;
+import org.neo4j.driver.TransactionConfig;
import org.neo4j.driver.exceptions.Neo4jException;
import org.neo4j.driver.summary.ResultSummary;
@@ -84,7 +85,9 @@ public class BoltSampler extends AbstractBoltTestElement implements Sampler, Tes
try {
res.setResponseHeaders("Cypher request: " + getCypher());
- res.setResponseData(execute(BoltConnectionElement.getDriver(), getCypher(), params, getAccessMode(), getDatabase()), StandardCharsets.UTF_8.name());
+ res.setResponseData(
+ execute(BoltConnectionElement.getDriver(), getCypher(), params,
+ getSessionConfig(), getTransactionConfig()), StandardCharsets.UTF_8.name());
} catch (Exception ex) {
res = handleException(res, ex);
} finally {
@@ -102,13 +105,9 @@ public class BoltSampler extends AbstractBoltTestElement implements Sampler, Tes
return APPLICABLE_CONFIG_CLASSES.contains(guiClass);
}
- private String execute(Driver driver, String cypher, Map<String, Object> params, AccessMode accessMode, String database) {
- SessionConfig sessionConfig = SessionConfig.builder()
- .withDatabase(database)
- .withDefaultAccessMode(accessMode)
- .build();
+ private String execute(Driver driver, String cypher, Map<String, Object> params, SessionConfig sessionConfig, TransactionConfig txConfig) {
try (Session session = driver.session(sessionConfig)) {
- Result statementResult = session.run(cypher, params);
+ Result statementResult = session.run(cypher, params, txConfig);
return response(statementResult);
}
}
@@ -141,12 +140,25 @@ public class BoltSampler extends AbstractBoltTestElement implements Sampler, Tes
.append(getCypher())
.append("\n")
.append("Parameters: \n")
- .append(getParams());
+ .append(getParams())
+ .append("\n")
+ .append("Database: \n")
+ .append(getDatabase())
+ .append("\n")
+ .append("Access Mode: \n")
+ .append(getAccessMode().toString());
return request.toString();
}
private String response(Result result) {
StringBuilder response = new StringBuilder();
+ List<Record> records;
+ if (isRecordQueryResults()) {
+ //get records already as consume() will exhaust the stream
+ records = result.list();
+ } else {
+ records = null;
+ }
response.append("\nSummary:");
ResultSummary summary = result.consume();
response.append("\nConstraints Added: ")
@@ -172,8 +184,8 @@ public class BoltSampler extends AbstractBoltTestElement implements Sampler, Tes
.append("\nRelationships Deleted: ")
.append(summary.counters().relationshipsDeleted());
response.append("\n\nRecords: ");
- if (isRecordQueryResults()) {
- for (Record record : result.list()) {
+ if (records != null) {
+ for (Record record : records) {
response.append("\n").append(record);
}
} else {
diff --git a/src/protocol/bolt/src/main/java/org/apache/jmeter/protocol/bolt/sampler/BoltTestElementBeanInfoSupport.java b/src/protocol/bolt/src/main/java/org/apache/jmeter/protocol/bolt/sampler/BoltTestElementBeanInfoSupport.java
index 1282b4b..cd07b8f 100644
--- a/src/protocol/bolt/src/main/java/org/apache/jmeter/protocol/bolt/sampler/BoltTestElementBeanInfoSupport.java
+++ b/src/protocol/bolt/src/main/java/org/apache/jmeter/protocol/bolt/sampler/BoltTestElementBeanInfoSupport.java
@@ -33,7 +33,8 @@ public abstract class BoltTestElementBeanInfoSupport extends BeanInfoSupport {
protected BoltTestElementBeanInfoSupport(Class<? extends TestBean> beanClass) {
super(beanClass);
- createPropertyGroup("query", new String[] { "cypher","params","recordQueryResults","accessMode","database"});
+ createPropertyGroup("query", new String[] { "cypher","params","recordQueryResults"});
+ createPropertyGroup("options", new String[] { "accessMode","database", "txTimeout"});
PropertyDescriptor propertyDescriptor = property("cypher", TypeEditor.TextAreaEditor);
propertyDescriptor.setValue(NOT_UNDEFINED, Boolean.TRUE);
@@ -50,9 +51,19 @@ public abstract class BoltTestElementBeanInfoSupport extends BeanInfoSupport {
propertyDescriptor = property("accessMode", TypeEditor.ComboStringEditor);
propertyDescriptor.setValue(NOT_UNDEFINED, Boolean.TRUE);
propertyDescriptor.setValue(NOT_EXPRESSION, Boolean.TRUE);
- propertyDescriptor.setValue(DEFAULT, AccessMode.WRITE);
+ propertyDescriptor.setValue(DEFAULT, AccessMode.WRITE.toString());
+ propertyDescriptor.setValue(TAGS, getListAccessModes());
- propertyDescriptor = property("database");
+ propertyDescriptor = property("database", TypeEditor.ComboStringEditor);
propertyDescriptor.setValue(DEFAULT, "neo4j");
+
+ propertyDescriptor = property("txTimeout");
+ propertyDescriptor.setValue(NOT_UNDEFINED, Boolean.TRUE);
+ propertyDescriptor.setValue(DEFAULT, "60");
+ }
+
+ private String[] getListAccessModes() {
+ String[] list = {AccessMode.READ.toString(), AccessMode.WRITE.toString()};
+ return list;
}
}
diff --git a/src/protocol/bolt/src/main/resources/org/apache/jmeter/protocol/bolt/sampler/BoltSamplerResources.properties b/src/protocol/bolt/src/main/resources/org/apache/jmeter/protocol/bolt/sampler/BoltSamplerResources.properties
index 7ace4c2..5e02808 100644
--- a/src/protocol/bolt/src/main/resources/org/apache/jmeter/protocol/bolt/sampler/BoltSamplerResources.properties
+++ b/src/protocol/bolt/src/main/resources/org/apache/jmeter/protocol/bolt/sampler/BoltSamplerResources.properties
@@ -17,6 +17,7 @@
displayName=Bolt Request
query.displayName=Query
+options.displayName=Options
cypher.displayName=Cypher Statement
cypher.shortDescription=Cypher Statement
params.displayName=Params
@@ -24,6 +25,8 @@ params.shortDescription=Params
recordQueryResults.displayName=Record Query Results
recordQueryResults.shortDescription=Records the results of queries and displays in listeners such as View Results Tree, this iterates through the entire resultset. Use to debug only.
accessMode.displayName=Access Mode
-accessMode.shortDescription=Access Mode
+accessMode.shortDescription=Whether it's a READ or WRITE query (affects query routing in clusters)
database.displayName=Database
-database.shortDescription=Database
+database.shortDescription=Neo4j 4.x : database to query (leave empty for 3.5)
+txTimeout.displayName=Transaction timeout
+txTimeout.shortDescription=Transaction timeout in seconds
diff --git a/src/protocol/bolt/src/test/groovy/org/apache/jmeter/protocol/bolt/sampler/BoltSamplerSpec.groovy b/src/protocol/bolt/src/test/groovy/org/apache/jmeter/protocol/bolt/sampler/BoltSamplerSpec.groovy
index 6f89702..80ce336 100644
--- a/src/protocol/bolt/src/test/groovy/org/apache/jmeter/protocol/bolt/sampler/BoltSamplerSpec.groovy
+++ b/src/protocol/bolt/src/test/groovy/org/apache/jmeter/protocol/bolt/sampler/BoltSamplerSpec.groovy
@@ -55,7 +55,8 @@ class BoltSamplerSpec extends Specification {
given:
sampler.setCypher("MATCH x")
sampler.setDatabase("neo4j")
- session.run("MATCH x", [:]) >> getEmptyQueryResult()
+ sampler.setTxTimeout(60)
+ session.run("MATCH x", [:], _) >> getEmptyQueryResult()
when:
def response = sampler.sample(entry)
then:
@@ -73,7 +74,8 @@ class BoltSamplerSpec extends Specification {
given:
sampler.setCypher("MATCH x")
sampler.setDatabase("neo4j")
- session.run("MATCH x", [:]) >> { throw new RuntimeException("a message") }
+ sampler.setTxTimeout(60)
+ session.run("MATCH x", [:], _) >> { throw new RuntimeException("a message") }
when:
def response = sampler.sample(entry)
then:
@@ -91,6 +93,8 @@ class BoltSamplerSpec extends Specification {
given:
sampler.setCypher("MATCH x")
sampler.setParams("{invalid}")
+ sampler.setDatabase("neo4j")
+ sampler.setTxTimeout(60)
when:
def response = sampler.sample(entry)
then:
@@ -108,7 +112,8 @@ class BoltSamplerSpec extends Specification {
given:
sampler.setCypher("MATCH x")
sampler.setDatabase("neo4j")
- session.run("MATCH x", [:]) >> { throw new ClientException("a code", "a message") }
+ sampler.setTxTimeout(60)
+ session.run("MATCH x", [:], _) >> { throw new ClientException("a code", "a message") }
when:
def response = sampler.sample(entry)
then: