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 <[email protected]> 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:
