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(&quot;owowo&quot;)</stringProp>
+          <stringProp name="params">{&quot;paramName&quot;:&quot;paramValue&quot;}</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: