From ce184b6603812c0ebad75a1fe0f2889f87c2d640 Mon Sep 17 00:00:00 2001 From: "rick.parker" Date: Tue, 31 Oct 2017 17:22:58 +0000 Subject: [PATCH 01/37] First working version of RPC & JMeter --- .idea/compiler.xml | 2 + settings.gradle | 1 + tools/jmeter/build.gradle | 58 + .../net/corda/jmeter/CordaRPCSampler.kt | 83 ++ .../main/kotlin/net/corda/jmeter/Launcher.kt | 13 + .../net/corda/jmeter/TraderDemoPlugins.kt | 47 + .../src/main/resources/Java Request.jmx | 177 +++ .../main/resources/bin/saveservice.properties | 412 ++++++ .../src/main/resources/bin/upgrade.properties | 123 ++ .../src/main/resources/jmeter.properties | 1242 +++++++++++++++++ 10 files changed, 2158 insertions(+) create mode 100644 tools/jmeter/build.gradle create mode 100644 tools/jmeter/src/main/kotlin/net/corda/jmeter/CordaRPCSampler.kt create mode 100644 tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt create mode 100644 tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt create mode 100644 tools/jmeter/src/main/resources/Java Request.jmx create mode 100644 tools/jmeter/src/main/resources/bin/saveservice.properties create mode 100644 tools/jmeter/src/main/resources/bin/upgrade.properties create mode 100644 tools/jmeter/src/main/resources/jmeter.properties diff --git a/.idea/compiler.xml b/.idea/compiler.xml index bafd46fea2..723b83cbb3 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -77,6 +77,8 @@ + + diff --git a/settings.gradle b/settings.gradle index e386b5bf3f..d9596e00a6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -34,6 +34,7 @@ include 'tools:explorer:capsule' include 'tools:demobench' include 'tools:loadtest' include 'tools:graphs' +include 'tools:jmeter' include 'example-code' project(':example-code').projectDir = file("$settingsDir/docs/source/example-code") include 'samples:attachment-demo' diff --git a/tools/jmeter/build.gradle b/tools/jmeter/build.gradle new file mode 100644 index 0000000000..150ec63690 --- /dev/null +++ b/tools/jmeter/build.gradle @@ -0,0 +1,58 @@ +apply plugin: 'kotlin' +apply plugin: 'application' +//apply plugin: 'net.corda.plugins.cordapp' +//apply plugin: 'net.corda.plugins.cordformation' + +mainClassName = 'net.corda.jmeter.Launcher' + +dependencies { + compile project(':client:rpc') + + // JMeter + ext.jmVersion = "3.3" + + runtime group: 'org.apache.jmeter', name: 'ApacheJMeter_components', version: "$jmVersion" + runtime group: 'org.apache.jmeter', name: 'ApacheJMeter_core', version: "$jmVersion" + runtime group: 'org.apache.jmeter', name: 'ApacheJMeter_ftp', version: "$jmVersion" + runtime group: 'org.apache.jmeter', name: 'ApacheJMeter_functions', version: "$jmVersion" + runtime group: 'org.apache.jmeter', name: 'ApacheJMeter_http', version: "$jmVersion" + compile group: 'org.apache.jmeter', name: 'ApacheJMeter_java', version: "$jmVersion" + runtime group: 'org.apache.jmeter', name: 'ApacheJMeter_jdbc', version: "$jmVersion" + runtime group: 'org.apache.jmeter', name: 'ApacheJMeter_jms', version: "$jmVersion" + runtime group: 'org.apache.jmeter', name: 'ApacheJMeter_junit', version: "$jmVersion" + runtime group: 'org.apache.jmeter', name: 'ApacheJMeter_ldap', version: "$jmVersion" + runtime group: 'org.apache.jmeter', name: 'ApacheJMeter_mail', version: "$jmVersion" + runtime group: 'org.apache.jmeter', name: 'ApacheJMeter_mongodb', version: "$jmVersion" + runtime group: 'org.apache.jmeter', name: 'ApacheJMeter_native', version: "$jmVersion" + runtime group: 'org.apache.jmeter', name: 'ApacheJMeter_tcp', version: "$jmVersion" + runtime group: 'org.apache.jmeter', name: 'ApacheJMeter_config', version: "$jmVersion" + runtime group: 'org.apache.jmeter', name: 'ApacheJMeter', version: "$jmVersion" + runtime group: 'org.apache.jmeter', name: 'jorphan', version: "$jmVersion" + + compile project(":test-utils") + compile project(":finance") + + //cordapp project(':finance') + //cordapp project(':samples:bank-of-corda-demo') + +} + +//jmeter { +// testFileDir = file("src/main/resources/jmeter") +//} + +run { + systemProperty "search_paths", project(':tools:jmeter').configurations.runtime.files.join(";") + //jvmArgs += "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005" + args+= [ "-p", sourceSets.main.resources.getSrcDirs().first().getPath()+"/jmeter.properties", + "-d", sourceSets.main.resources.getSrcDirs().first().getPath(), + "-j", buildDir.getPath()+"/jmeter.log" ] +} + +jar { + manifest { + attributes( + 'Automatic-Module-Name': 'net.corda.tools.jmeter' + ) + } +} \ No newline at end of file diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/CordaRPCSampler.kt b/tools/jmeter/src/main/kotlin/net/corda/jmeter/CordaRPCSampler.kt new file mode 100644 index 0000000000..b6d385fec1 --- /dev/null +++ b/tools/jmeter/src/main/kotlin/net/corda/jmeter/CordaRPCSampler.kt @@ -0,0 +1,83 @@ +package net.corda.jmeter + +import net.corda.client.rpc.CordaRPCClient +import net.corda.client.rpc.CordaRPCConnection +import net.corda.core.flows.FlowLogic +import net.corda.core.messaging.CordaRPCOps +import net.corda.core.utilities.NetworkHostAndPort +import org.apache.jmeter.config.Argument +import org.apache.jmeter.config.Arguments +import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient +import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext +import org.apache.jmeter.samplers.SampleResult + + +class CordaRPCSampler() : AbstractJavaSamplerClient() { + companion object { + val host = Argument("host", "localhost", "", "The remote network address (hostname or IP address) to connect to for RPC.") + val port = Argument("port", "10000", "", "The remote port to connect to for RPC.") + val username = Argument("username", "corda", "", "The RPC user to connect to connect as.") + val password = Argument("password", "corda_is_awesome", "", "The password for the RPC user.") + val className = Argument("pluginClassName", "", "", "The class name of the implementation of ${CordaRPCSampler.Plugin::class.java}.") + + val allArgs = setOf(host, port, username, password, className) + } + + var rpcClient: CordaRPCClient? = null + var rpcConnection: CordaRPCConnection? = null + var rpcProxy: CordaRPCOps? = null + var plugin: Plugin? = null + + override fun getDefaultParameters(): Arguments { + // Add copies of all args, since they seem to be mutable. + return Arguments().apply { for(arg in allArgs) { addArgument(arg.clone() as Argument) } } + } + + override fun setupTest(context: JavaSamplerContext) { + super.setupTest(context) + rpcClient = CordaRPCClient(NetworkHostAndPort(context.getParameter(host.name), context.getIntParameter(port.name))) + rpcConnection = rpcClient!!.start(context.getParameter(username.name), context.getParameter(password.name)) + rpcProxy = rpcConnection!!.proxy + plugin = Class.forName(context.getParameter(className.name)).newInstance() as Plugin + plugin!!.setupTest(rpcProxy!!, context) + } + + override fun runTest(context: JavaSamplerContext): SampleResult { + val flowInvoke = plugin!!.createFlowInvoke(rpcProxy!!, context) + val result = SampleResult() + result.sampleStart() + val handle = rpcProxy!!.startFlowDynamic(flowInvoke!!.flowLogicClass, *(flowInvoke!!.args)) + result.sampleLabel = handle.id.toString() + result.latencyEnd() + try { + val flowResult = handle.returnValue.get() + result.sampleEnd() + return result.apply { + isSuccessful = true + } + } catch(e: Exception) { + result.sampleEnd() + return result.apply { + isSuccessful = false + } + } + } + + override fun teardownTest(context: JavaSamplerContext) { + plugin!!.teardownTest(rpcProxy!!, context) + plugin = null + rpcProxy = null + rpcConnection!!.close() + rpcConnection = null + rpcClient = null + super.teardownTest(context) + } + + interface Plugin { + fun setupTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) + fun createFlowInvoke(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext): FlowInvoke<*> + fun teardownTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) + } + + class FlowInvoke>(val flowLogicClass: Class, val args: Array) +} \ No newline at end of file diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt new file mode 100644 index 0000000000..47787db764 --- /dev/null +++ b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt @@ -0,0 +1,13 @@ +package net.corda.jmeter + +import org.apache.jmeter.JMeter + +class Launcher { + companion object { + @JvmStatic + fun main(args: Array) { + val jmeter = JMeter() + jmeter.start(args) + } + } +} \ No newline at end of file diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt b/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt new file mode 100644 index 0000000000..1e91443840 --- /dev/null +++ b/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt @@ -0,0 +1,47 @@ +package net.corda.jmeter + +import net.corda.core.identity.Party +import net.corda.core.messaging.CordaRPCOps +import net.corda.core.messaging.startFlow +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.getOrThrow +import net.corda.finance.DOLLARS +import net.corda.finance.flows.CashIssueFlow +import net.corda.jmeter.CordaRPCSampler.FlowInvoke +import net.corda.testing.DUMMY_BANK_A +import net.corda.testing.DUMMY_BANK_B +import net.corda.testing.contracts.calculateRandomlySizedAmounts +import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext +import java.util.* + + +abstract class AsbtractTraderDemoPlugin : CordaRPCSampler.Plugin { + + lateinit var buyer: Party + lateinit var seller: Party + lateinit var notary: Party + + protected fun getIdentities(rpc: CordaRPCOps) { + buyer = rpc.wellKnownPartyFromX500Name(DUMMY_BANK_A.name) ?: throw IllegalStateException("Don't know ${DUMMY_BANK_A.name}") + seller = rpc.wellKnownPartyFromX500Name(DUMMY_BANK_B.name) ?: throw IllegalStateException("Don't know ${DUMMY_BANK_B.name}") + notary = rpc.notaryIdentities().first() + } + +} + +class CashIssuerPlugin : AsbtractTraderDemoPlugin() { + override fun setupTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) { + getIdentities(rpcProxy) + } + + override fun teardownTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) { + } + + override fun createFlowInvoke(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext): FlowInvoke { + val amount = 1_100_000_000_000.DOLLARS + //val amounts = calculateRandomlySizedAmounts(amount, 3, 10, Random()) + //rpc.startFlow(net.corda.finance.flows::CashIssueFlow, amount, OpaqueBytes.of(1), notary).returnValue.getOrThrow() + return FlowInvoke(CashIssueFlow::class.java, arrayOf(amount, OpaqueBytes.of(1), notary)) + } + +} \ No newline at end of file diff --git a/tools/jmeter/src/main/resources/Java Request.jmx b/tools/jmeter/src/main/resources/Java Request.jmx new file mode 100644 index 0000000000..974d09bfac --- /dev/null +++ b/tools/jmeter/src/main/resources/Java Request.jmx @@ -0,0 +1,177 @@ + + + + + + false + false + + + + + + + + continue + + false + 1000 + + 2 + + 1509455820000 + 1509455820000 + false + + + + + + + + + host + localhost + = + + + port + 10012 + = + + + username + demo + = + + + password + demo + = + + + pluginClassName + net.corda.jmeter.CashIssuerPlugin + = + + + + net.corda.jmeter.CordaRPCSampler + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + true + + + + diff --git a/tools/jmeter/src/main/resources/bin/saveservice.properties b/tools/jmeter/src/main/resources/bin/saveservice.properties new file mode 100644 index 0000000000..3e2b5fb4e1 --- /dev/null +++ b/tools/jmeter/src/main/resources/bin/saveservice.properties @@ -0,0 +1,412 @@ +#--------------------------------------------------------- +# SAVESERVICE PROPERTIES - JMETER INTERNAL USE ONLY +#--------------------------------------------------------- + +## Licensed to the Apache Software Foundation (ASF) under one or more +## contributor license agreements. See the NOTICE file distributed with +## this work for additional information regarding copyright ownership. +## The ASF licenses this file to You under the Apache License, Version 2.0 +## (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. + +# This file is used to define how XStream (de-)serializes classnames +# in JMX test plan files. + +# FOR JMETER INTERNAL USE ONLY + +#--------------------------------------------------------- + +# N.B. To ensure backward compatibility, please do NOT change or delete any entries + +# New entries can be added as necessary. +# +# Note that keys starting with an underscore are special, +# and are not used as aliases. +# +# Please keep the entries in alphabetical order within the sections +# to reduce the likelihood of duplicates +# +# version number of this file is now computed by a sha1 sum, so no need for +# an explicit _file_version property anymore. +# +# For this sha1 sum we ignore every newline character. It can be computed +# by the following command: +# +# cat bin/saveservice.properties | perl -ne 'chomp; print' | sha1sum +# +# Be aware, that every change in this file will change the sha1 sum! +# +# Conversion version (for JMX output files) +# Must be updated if the file has been changed since the previous release +# Format is: +# Save service version=JMeter version at which change occurred +# 1.7 = 2.1.1 +# 1.8 = 2.1.2 +# (Some version updates were missed here...) +# 2.0 = 2.3.1 +# 2.1 = 2.3.2 +# (Some version updates were missed here...) +# 2.2 = 2.6 +# 2.3 = 2.7 +# 2.4 = 2.9 +# 2.5 = 2.10 +# 2.6 = 2.11 +# 2.7 = 2.12 +# 2.8 = 2.13 +# 2.9 = 2.14 +# 3.1 = 3.1 +# 3.2 = 3.2 +_version=3.2 +# +# +# Character set encoding used to read and write JMeter XML files and CSV results +# +_file_encoding=UTF-8 +# +#--------------------------------------------------------- +# +# The following properties are used to create aliases +# [Must all start with capital letter] +# +AccessLogSampler=org.apache.jmeter.protocol.http.sampler.AccessLogSampler +AjpSampler=org.apache.jmeter.protocol.http.sampler.AjpSampler +AjpSamplerGui=org.apache.jmeter.protocol.http.control.gui.AjpSamplerGui +AnchorModifier=org.apache.jmeter.protocol.http.modifier.AnchorModifier +AnchorModifierGui=org.apache.jmeter.protocol.http.modifier.gui.AnchorModifierGui +Argument=org.apache.jmeter.config.Argument +Arguments=org.apache.jmeter.config.Arguments +ArgumentsPanel=org.apache.jmeter.config.gui.ArgumentsPanel +AssertionGui=org.apache.jmeter.assertions.gui.AssertionGui +AssertionVisualizer=org.apache.jmeter.visualizers.AssertionVisualizer +AuthManager=org.apache.jmeter.protocol.http.control.AuthManager +Authorization=org.apache.jmeter.protocol.http.control.Authorization +AuthPanel=org.apache.jmeter.protocol.http.gui.AuthPanel +BackendListener=org.apache.jmeter.visualizers.backend.BackendListener +BackendListenerGui=org.apache.jmeter.visualizers.backend.BackendListenerGui +BeanShellAssertion=org.apache.jmeter.assertions.BeanShellAssertion +BeanShellAssertionGui=org.apache.jmeter.assertions.gui.BeanShellAssertionGui +BeanShellListener=org.apache.jmeter.visualizers.BeanShellListener +BeanShellPostProcessor=org.apache.jmeter.extractor.BeanShellPostProcessor +BeanShellPreProcessor=org.apache.jmeter.modifiers.BeanShellPreProcessor +BeanShellSampler=org.apache.jmeter.protocol.java.sampler.BeanShellSampler +BeanShellSamplerGui=org.apache.jmeter.protocol.java.control.gui.BeanShellSamplerGui +BeanShellTimer=org.apache.jmeter.timers.BeanShellTimer +BSFAssertion=org.apache.jmeter.assertions.BSFAssertion +BSFListener=org.apache.jmeter.visualizers.BSFListener +BSFPreProcessor=org.apache.jmeter.modifiers.BSFPreProcessor +BSFPostProcessor=org.apache.jmeter.extractor.BSFPostProcessor +BSFSampler=org.apache.jmeter.protocol.java.sampler.BSFSampler +BSFSamplerGui=org.apache.jmeter.protocol.java.control.gui.BSFSamplerGui +BSFTimer=org.apache.jmeter.timers.BSFTimer +CacheManager=org.apache.jmeter.protocol.http.control.CacheManager +CacheManagerGui=org.apache.jmeter.protocol.http.gui.CacheManagerGui +CompareAssertion=org.apache.jmeter.assertions.CompareAssertion +ComparisonVisualizer=org.apache.jmeter.visualizers.ComparisonVisualizer +ConfigTestElement=org.apache.jmeter.config.ConfigTestElement +ConstantThroughputTimer=org.apache.jmeter.timers.ConstantThroughputTimer +ConstantTimer=org.apache.jmeter.timers.ConstantTimer +ConstantTimerGui=org.apache.jmeter.timers.gui.ConstantTimerGui +Cookie=org.apache.jmeter.protocol.http.control.Cookie +CookieManager=org.apache.jmeter.protocol.http.control.CookieManager +CookiePanel=org.apache.jmeter.protocol.http.gui.CookiePanel +CounterConfig=org.apache.jmeter.modifiers.CounterConfig +CriticalSectionController=org.apache.jmeter.control.CriticalSectionController +CriticalSectionControllerGui=org.apache.jmeter.control.gui.CriticalSectionControllerGui +CounterConfigGui=org.apache.jmeter.modifiers.gui.CounterConfigGui +CSVDataSet=org.apache.jmeter.config.CSVDataSet +DebugPostProcessor=org.apache.jmeter.extractor.DebugPostProcessor +DebugSampler=org.apache.jmeter.sampler.DebugSampler +# removed in 3.1, class was deleted in r1763837 +DistributionGraphVisualizer=org.apache.jmeter.visualizers.DistributionGraphVisualizer +DNSCacheManager=org.apache.jmeter.protocol.http.control.DNSCacheManager +DNSCachePanel=org.apache.jmeter.protocol.http.gui.DNSCachePanel +DurationAssertion=org.apache.jmeter.assertions.DurationAssertion +DurationAssertionGui=org.apache.jmeter.assertions.gui.DurationAssertionGui +# Should really have been defined as floatProp to agree with other properties +# No point changing this now +FloatProperty=org.apache.jmeter.testelement.property.FloatProperty +ForeachController=org.apache.jmeter.control.ForeachController +ForeachControlPanel=org.apache.jmeter.control.gui.ForeachControlPanel +FtpConfigGui=org.apache.jmeter.protocol.ftp.config.gui.FtpConfigGui +FTPSampler=org.apache.jmeter.protocol.ftp.sampler.FTPSampler +FtpTestSamplerGui=org.apache.jmeter.protocol.ftp.control.gui.FtpTestSamplerGui +GaussianRandomTimer=org.apache.jmeter.timers.GaussianRandomTimer +GaussianRandomTimerGui=org.apache.jmeter.timers.gui.GaussianRandomTimerGui +GenericController=org.apache.jmeter.control.GenericController +GraphAccumVisualizer=org.apache.jmeter.visualizers.GraphAccumVisualizer +GraphVisualizer=org.apache.jmeter.visualizers.GraphVisualizer +Header=org.apache.jmeter.protocol.http.control.Header +HeaderManager=org.apache.jmeter.protocol.http.control.HeaderManager +HeaderPanel=org.apache.jmeter.protocol.http.gui.HeaderPanel +HTMLAssertion=org.apache.jmeter.assertions.HTMLAssertion +HTMLAssertionGui=org.apache.jmeter.assertions.gui.HTMLAssertionGui +HTTPArgument=org.apache.jmeter.protocol.http.util.HTTPArgument +HTTPArgumentsPanel=org.apache.jmeter.protocol.http.gui.HTTPArgumentsPanel +HTTPFileArg=org.apache.jmeter.protocol.http.util.HTTPFileArg +HTTPFileArgs=org.apache.jmeter.protocol.http.util.HTTPFileArgs +HttpDefaultsGui=org.apache.jmeter.protocol.http.config.gui.HttpDefaultsGui +HtmlExtractor=org.apache.jmeter.extractor.HtmlExtractor +HtmlExtractorGui=org.apache.jmeter.extractor.gui.HtmlExtractorGui +# removed in r1039684, probably not released. Not present in r322831 or since. +#HttpGenericSampler=org.apache.jmeter.protocol.http.sampler.HttpGenericSampler +# removed in r1039684, probably not released. Not present in r322831 or since. +#HttpGenericSamplerGui=org.apache.jmeter.protocol.http.control.gui.HttpGenericSamplerGui +HttpMirrorControl=org.apache.jmeter.protocol.http.control.HttpMirrorControl +HttpMirrorControlGui=org.apache.jmeter.protocol.http.control.gui.HttpMirrorControlGui +# r397955 - removed test class. Keep as commented entry for info only. +#HTTPNullSampler=org.apache.jmeter.protocol.http.sampler.HTTPNullSampler +# Merge previous 2 HTTP samplers into one +HTTPSampler_=org.apache.jmeter.protocol.http.sampler.HTTPSampler +HTTPSampler2_=org.apache.jmeter.protocol.http.sampler.HTTPSampler2 +HTTPSamplerProxy,HTTPSampler,HTTPSampler2=org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy +# Merge GUIs +HttpTestSampleGui,HttpTestSampleGui2=org.apache.jmeter.protocol.http.control.gui.HttpTestSampleGui +#HttpTestSampleGui2=org.apache.jmeter.protocol.http.control.gui.HttpTestSampleGui2 +IfController=org.apache.jmeter.control.IfController +IfControllerPanel=org.apache.jmeter.control.gui.IfControllerPanel +IncludeController=org.apache.jmeter.control.IncludeController +IncludeControllerGui=org.apache.jmeter.control.gui.IncludeControllerGui +InterleaveControl=org.apache.jmeter.control.InterleaveControl +InterleaveControlGui=org.apache.jmeter.control.gui.InterleaveControlGui +JavaConfig=org.apache.jmeter.protocol.java.config.JavaConfig +JavaConfigGui=org.apache.jmeter.protocol.java.config.gui.JavaConfigGui +JavaSampler=org.apache.jmeter.protocol.java.sampler.JavaSampler +JavaTest=org.apache.jmeter.protocol.java.test.JavaTest +JavaTestSamplerGui=org.apache.jmeter.protocol.java.control.gui.JavaTestSamplerGui +JDBCDataSource=org.apache.jmeter.protocol.jdbc.config.DataSourceElement +JDBCPostProcessor=org.apache.jmeter.protocol.jdbc.processor.JDBCPostProcessor +JDBCPreProcessor=org.apache.jmeter.protocol.jdbc.processor.JDBCPreProcessor +JDBCSampler=org.apache.jmeter.protocol.jdbc.sampler.JDBCSampler +# Renamed to JMSSamplerGui; keep original entry for backwards compatibility +JMSConfigGui=org.apache.jmeter.protocol.jms.control.gui.JMSConfigGui +JMSProperties=org.apache.jmeter.protocol.jms.sampler.JMSProperties +JMSProperty=org.apache.jmeter.protocol.jms.sampler.JMSProperty +JMSPublisherGui=org.apache.jmeter.protocol.jms.control.gui.JMSPublisherGui +JMSSampler=org.apache.jmeter.protocol.jms.sampler.JMSSampler +JMSSamplerGui=org.apache.jmeter.protocol.jms.control.gui.JMSSamplerGui +JMSSubscriberGui=org.apache.jmeter.protocol.jms.control.gui.JMSSubscriberGui +JSONPostProcessor=org.apache.jmeter.extractor.json.jsonpath.JSONPostProcessor +JSONPostProcessorGui=org.apache.jmeter.extractor.json.jsonpath.gui.JSONPostProcessorGui +# Removed in r545311 as Jndi no longer present; keep for compat. +JndiDefaultsGui=org.apache.jmeter.protocol.jms.control.gui.JndiDefaultsGui +JSR223Assertion=org.apache.jmeter.assertions.JSR223Assertion +JSR223Listener=org.apache.jmeter.visualizers.JSR223Listener +JSR223PostProcessor=org.apache.jmeter.extractor.JSR223PostProcessor +JSR223PreProcessor=org.apache.jmeter.modifiers.JSR223PreProcessor +JSR223Sampler=org.apache.jmeter.protocol.java.sampler.JSR223Sampler +JSR223Timer=org.apache.jmeter.timers.JSR223Timer +JUnitSampler=org.apache.jmeter.protocol.java.sampler.JUnitSampler +JUnitTestSamplerGui=org.apache.jmeter.protocol.java.control.gui.JUnitTestSamplerGui +KeystoreConfig=org.apache.jmeter.config.KeystoreConfig +LDAPArgument=org.apache.jmeter.protocol.ldap.config.gui.LDAPArgument +LDAPArguments=org.apache.jmeter.protocol.ldap.config.gui.LDAPArguments +LDAPArgumentsPanel=org.apache.jmeter.protocol.ldap.config.gui.LDAPArgumentsPanel +LdapConfigGui=org.apache.jmeter.protocol.ldap.config.gui.LdapConfigGui +LdapExtConfigGui=org.apache.jmeter.protocol.ldap.config.gui.LdapExtConfigGui +LDAPExtSampler=org.apache.jmeter.protocol.ldap.sampler.LDAPExtSampler +LdapExtTestSamplerGui=org.apache.jmeter.protocol.ldap.control.gui.LdapExtTestSamplerGui +LDAPSampler=org.apache.jmeter.protocol.ldap.sampler.LDAPSampler +LdapTestSamplerGui=org.apache.jmeter.protocol.ldap.control.gui.LdapTestSamplerGui +LogicControllerGui=org.apache.jmeter.control.gui.LogicControllerGui +LoginConfig=org.apache.jmeter.config.LoginConfig +LoginConfigGui=org.apache.jmeter.config.gui.LoginConfigGui +LoopController=org.apache.jmeter.control.LoopController +LoopControlPanel=org.apache.jmeter.control.gui.LoopControlPanel +MailerModel=org.apache.jmeter.reporters.MailerModel +MailerResultCollector=org.apache.jmeter.reporters.MailerResultCollector +MailerVisualizer=org.apache.jmeter.visualizers.MailerVisualizer +MailReaderSampler=org.apache.jmeter.protocol.mail.sampler.MailReaderSampler +MailReaderSamplerGui=org.apache.jmeter.protocol.mail.sampler.gui.MailReaderSamplerGui +MD5HexAssertion=org.apache.jmeter.assertions.MD5HexAssertion +MD5HexAssertionGUI=org.apache.jmeter.assertions.gui.MD5HexAssertionGUI +ModuleController=org.apache.jmeter.control.ModuleController +ModuleControllerGui=org.apache.jmeter.control.gui.ModuleControllerGui +MongoScriptSampler=org.apache.jmeter.protocol.mongodb.sampler.MongoScriptSampler +MongoSourceElement=org.apache.jmeter.protocol.mongodb.config.MongoSourceElement + +# removed in 3.2, class was deleted in r +MonitorHealthVisualizer=org.apache.jmeter.visualizers.MonitorHealthVisualizer + +NamePanel=org.apache.jmeter.gui.NamePanel +ObsoleteGui=org.apache.jmeter.config.gui.ObsoleteGui +OnceOnlyController=org.apache.jmeter.control.OnceOnlyController +OnceOnlyControllerGui=org.apache.jmeter.control.gui.OnceOnlyControllerGui +# removed in 3.0, class was deleted in r1722962 +ParamMask=org.apache.jmeter.protocol.http.modifier.ParamMask +# removed in 3.0, class was deleted in r1722757 +ParamModifier=org.apache.jmeter.protocol.http.modifier.ParamModifier +# removed in 3.0, class was deleted in r1722757 +ParamModifierGui=org.apache.jmeter.protocol.http.modifier.gui.ParamModifierGui +PoissonRandomTimer=org.apache.jmeter.timers.PoissonRandomTimer +PoissonRandomTimerGui=org.apache.jmeter.timers.gui.PoissonRandomTimerGui +PropertyControlGui=org.apache.jmeter.visualizers.PropertyControlGui +ProxyControl=org.apache.jmeter.protocol.http.proxy.ProxyControl +ProxyControlGui=org.apache.jmeter.protocol.http.proxy.gui.ProxyControlGui +PublisherSampler=org.apache.jmeter.protocol.jms.sampler.PublisherSampler +RandomControlGui=org.apache.jmeter.control.gui.RandomControlGui +RandomController=org.apache.jmeter.control.RandomController +RandomOrderController=org.apache.jmeter.control.RandomOrderController +RandomOrderControllerGui=org.apache.jmeter.control.gui.RandomOrderControllerGui +RandomVariableConfig=org.apache.jmeter.config.RandomVariableConfig +RecordController=org.apache.jmeter.protocol.http.control.gui.RecordController +RecordingController=org.apache.jmeter.protocol.http.control.RecordingController +# removed in r1039684, class was deleted in r580452 +ReflectionThreadGroup=org.apache.jmeter.threads.ReflectionThreadGroup +RegexExtractor=org.apache.jmeter.extractor.RegexExtractor +RegexExtractorGui=org.apache.jmeter.extractor.gui.RegexExtractorGui +RegExUserParameters=org.apache.jmeter.protocol.http.modifier.RegExUserParameters +RegExUserParametersGui=org.apache.jmeter.protocol.http.modifier.gui.RegExUserParametersGui +RemoteListenerWrapper=org.apache.jmeter.samplers.RemoteListenerWrapper +RemoteSampleListenerWrapper=org.apache.jmeter.samplers.RemoteSampleListenerWrapper +RemoteTestListenerWrapper=org.apache.jmeter.samplers.RemoteTestListenerWrapper +RemoteThreadsListenerWrapper=org.apache.jmeter.threads.RemoteThreadsListenerWrapper +ResponseAssertion=org.apache.jmeter.assertions.ResponseAssertion +RespTimeGraphVisualizer=org.apache.jmeter.visualizers.RespTimeGraphVisualizer +ResultAction=org.apache.jmeter.reporters.ResultAction +ResultActionGui=org.apache.jmeter.reporters.gui.ResultActionGui +ResultCollector=org.apache.jmeter.reporters.ResultCollector +ResultSaver=org.apache.jmeter.reporters.ResultSaver +ResultSaverGui=org.apache.jmeter.reporters.gui.ResultSaverGui +RunTime=org.apache.jmeter.control.RunTime +RunTimeGui=org.apache.jmeter.control.gui.RunTimeGui +SampleSaveConfiguration=org.apache.jmeter.samplers.SampleSaveConfiguration +SampleTimeout=org.apache.jmeter.modifiers.SampleTimeout +SampleTimeoutGui=org.apache.jmeter.modifiers.gui.SampleTimeoutGui +SimpleConfigGui=org.apache.jmeter.config.gui.SimpleConfigGui +SimpleDataWriter=org.apache.jmeter.visualizers.SimpleDataWriter +SizeAssertion=org.apache.jmeter.assertions.SizeAssertion +SizeAssertionGui=org.apache.jmeter.assertions.gui.SizeAssertionGui +SMIMEAssertion=org.apache.jmeter.assertions.SMIMEAssertionTestElement +SMIMEAssertionGui=org.apache.jmeter.assertions.gui.SMIMEAssertionGui +SmtpSampler=org.apache.jmeter.protocol.smtp.sampler.SmtpSampler +SmtpSamplerGui=org.apache.jmeter.protocol.smtp.sampler.gui.SmtpSamplerGui + +# removed in 3.2, class was deleted in r +SoapSampler=org.apache.jmeter.protocol.http.sampler.SoapSampler +# removed in 3.2, class was deleted in r +SoapSamplerGui=org.apache.jmeter.protocol.http.control.gui.SoapSamplerGui + +# removed in 3.1, class was deleted in r1763837 +SplineVisualizer=org.apache.jmeter.visualizers.SplineVisualizer +# Originally deleted in r397955 as class is obsolete; needed for compat. +SqlConfigGui=org.apache.jmeter.protocol.jdbc.config.gui.SqlConfigGui +StaticHost=org.apache.jmeter.protocol.http.control.StaticHost +StatGraphVisualizer=org.apache.jmeter.visualizers.StatGraphVisualizer +StatVisualizer=org.apache.jmeter.visualizers.StatVisualizer +SubscriberSampler=org.apache.jmeter.protocol.jms.sampler.SubscriberSampler +SubstitutionElement=org.apache.jmeter.assertions.SubstitutionElement +Summariser=org.apache.jmeter.reporters.Summariser +SummariserGui=org.apache.jmeter.reporters.gui.SummariserGui +SummaryReport=org.apache.jmeter.visualizers.SummaryReport +SwitchController=org.apache.jmeter.control.SwitchController +SwitchControllerGui=org.apache.jmeter.control.gui.SwitchControllerGui +SyncTimer=org.apache.jmeter.timers.SyncTimer +SystemSampler=org.apache.jmeter.protocol.system.SystemSampler +SystemSamplerGui=org.apache.jmeter.protocol.system.gui.SystemSamplerGui +TableVisualizer=org.apache.jmeter.visualizers.TableVisualizer +TCPConfigGui=org.apache.jmeter.protocol.tcp.config.gui.TCPConfigGui +TCPSampler=org.apache.jmeter.protocol.tcp.sampler.TCPSampler +TCPSamplerGui=org.apache.jmeter.protocol.tcp.control.gui.TCPSamplerGui +TestAction=org.apache.jmeter.sampler.TestAction +TestActionGui=org.apache.jmeter.sampler.gui.TestActionGui +TestBeanGUI=org.apache.jmeter.testbeans.gui.TestBeanGUI +TestFragmentController=org.apache.jmeter.control.TestFragmentController +TestFragmentControllerGui=org.apache.jmeter.control.gui.TestFragmentControllerGui +TestPlan=org.apache.jmeter.testelement.TestPlan +TestPlanGui=org.apache.jmeter.control.gui.TestPlanGui +ThreadGroup=org.apache.jmeter.threads.ThreadGroup +ThreadGroupGui=org.apache.jmeter.threads.gui.ThreadGroupGui +PostThreadGroup=org.apache.jmeter.threads.PostThreadGroup +PostThreadGroupGui=org.apache.jmeter.threads.gui.PostThreadGroupGui +SetupThreadGroup=org.apache.jmeter.threads.SetupThreadGroup +SetupThreadGroupGui=org.apache.jmeter.threads.gui.SetupThreadGroupGui +ThroughputController=org.apache.jmeter.control.ThroughputController +ThroughputControllerGui=org.apache.jmeter.control.gui.ThroughputControllerGui +TransactionController=org.apache.jmeter.control.TransactionController +TransactionControllerGui=org.apache.jmeter.control.gui.TransactionControllerGui +TransactionSampler=org.apache.jmeter.control.TransactionSampler +UniformRandomTimer=org.apache.jmeter.timers.UniformRandomTimer +UniformRandomTimerGui=org.apache.jmeter.timers.gui.UniformRandomTimerGui +URLRewritingModifier=org.apache.jmeter.protocol.http.modifier.URLRewritingModifier +URLRewritingModifierGui=org.apache.jmeter.protocol.http.modifier.gui.URLRewritingModifierGui +UserParameterModifier=org.apache.jmeter.protocol.http.modifier.UserParameterModifier +UserParameterModifierGui=org.apache.jmeter.protocol.http.modifier.gui.UserParameterModifierGui +UserParameters=org.apache.jmeter.modifiers.UserParameters +UserParametersGui=org.apache.jmeter.modifiers.gui.UserParametersGui +ViewResultsFullVisualizer=org.apache.jmeter.visualizers.ViewResultsFullVisualizer +# removed in 3.0, class was deleted in r1722757 +WebServiceSampler=org.apache.jmeter.protocol.http.sampler.WebServiceSampler +# removed in 3.0, class was deleted in r1722757 +WebServiceSamplerGui=org.apache.jmeter.protocol.http.control.gui.WebServiceSamplerGui +WhileController=org.apache.jmeter.control.WhileController +WhileControllerGui=org.apache.jmeter.control.gui.WhileControllerGui +WorkBench=org.apache.jmeter.testelement.WorkBench +WorkBenchGui=org.apache.jmeter.control.gui.WorkBenchGui +XMLAssertion=org.apache.jmeter.assertions.XMLAssertion +XMLAssertionGui=org.apache.jmeter.assertions.gui.XMLAssertionGui +XMLSchemaAssertion=org.apache.jmeter.assertions.XMLSchemaAssertion +XMLSchemaAssertionGUI=org.apache.jmeter.assertions.gui.XMLSchemaAssertionGUI +XPathAssertion=org.apache.jmeter.assertions.XPathAssertion +XPathAssertionGui=org.apache.jmeter.assertions.gui.XPathAssertionGui +XPathExtractor=org.apache.jmeter.extractor.XPathExtractor +XPathExtractorGui=org.apache.jmeter.extractor.gui.XPathExtractorGui +# +# Properties - all start with lower case letter and end with Prop +# +boolProp=org.apache.jmeter.testelement.property.BooleanProperty +collectionProp=org.apache.jmeter.testelement.property.CollectionProperty +doubleProp=org.apache.jmeter.testelement.property.DoubleProperty +elementProp=org.apache.jmeter.testelement.property.TestElementProperty +# see above - already defined as FloatProperty +#floatProp=org.apache.jmeter.testelement.property.FloatProperty +intProp=org.apache.jmeter.testelement.property.IntegerProperty +longProp=org.apache.jmeter.testelement.property.LongProperty +mapProp=org.apache.jmeter.testelement.property.MapProperty +objProp=org.apache.jmeter.testelement.property.ObjectProperty +stringProp=org.apache.jmeter.testelement.property.StringProperty +# +# Other - must start with a lower case letter (and not end with Prop) +# (otherwise they could clash with the initial set of aliases) +# +hashTree=org.apache.jorphan.collections.ListedHashTree +jmeterTestPlan=org.apache.jmeter.save.ScriptWrapper +sample=org.apache.jmeter.samplers.SampleResult +httpSample=org.apache.jmeter.protocol.http.sampler.HTTPSampleResult +statSample=org.apache.jmeter.samplers.StatisticalSampleResult +testResults=org.apache.jmeter.save.TestResultWrapper +assertionResult=org.apache.jmeter.assertions.AssertionResult + +# removed in 3.2, class was deleted in r +monitorStats=org.apache.jmeter.visualizers.MonitorStats +sampleEvent=org.apache.jmeter.samplers.SampleEvent +# +# Converters to register. Must start line with '_' +# If the converter is a collection of subitems, set equal to "collection" +# If the converter needs to know the class mappings but is not a collection of +# subitems, set it equal to "mapping" +_org.apache.jmeter.protocol.http.sampler.HTTPSamplerBaseConverter=collection +_org.apache.jmeter.protocol.http.util.HTTPResultConverter=collection +_org.apache.jmeter.save.converters.BooleanPropertyConverter= +_org.apache.jmeter.save.converters.IntegerPropertyConverter= +_org.apache.jmeter.save.converters.LongPropertyConverter= +_org.apache.jmeter.save.converters.MultiPropertyConverter=collection +_org.apache.jmeter.save.converters.SampleEventConverter= +_org.apache.jmeter.save.converters.SampleResultConverter=collection +_org.apache.jmeter.save.converters.SampleSaveConfigurationConverter=collection +_org.apache.jmeter.save.converters.StringPropertyConverter= +_org.apache.jmeter.save.converters.HashTreeConverter=collection +_org.apache.jmeter.save.converters.TestElementConverter=collection +_org.apache.jmeter.save.converters.TestElementPropertyConverter=collection +_org.apache.jmeter.save.converters.TestResultWrapperConverter=collection +_org.apache.jmeter.save.ScriptWrapperConverter=mapping +# +# Remember to update the _version entry +# diff --git a/tools/jmeter/src/main/resources/bin/upgrade.properties b/tools/jmeter/src/main/resources/bin/upgrade.properties new file mode 100644 index 0000000000..97dc396504 --- /dev/null +++ b/tools/jmeter/src/main/resources/bin/upgrade.properties @@ -0,0 +1,123 @@ +# Class, property and value upgrade equivalences. + +## Licensed to the Apache Software Foundation (ASF) under one or more +## contributor license agreements. See the NOTICE file distributed with +## this work for additional information regarding copyright ownership. +## The ASF licenses this file to You under the Apache License, Version 2.0 +## (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. + +# +# Format is as follows -- +# for renamed test element & GUI classes: +# old.class.Name=new.class.Name +# old.class.Name|guiClassName=new.class.Name +# (e.g. for ConfigTestElement) +# +# for renamed / deleted properties: +# class.Name/Old.propertyName=newPropertyName +# if newPropertyName is omitted, then property is deleted +# +# for renamed values: +# old.class.Name.old.propertyName/oldValue=newValue +# + +org.apache.jmeter.protocol.http.config.gui.UrlConfigGui=org.apache.jmeter.protocol.http.config.gui.HttpDefaultsGui +org.apache.jmeter.assertions.Assertion=org.apache.jmeter.assertions.ResponseAssertion +org.apache.jmeter.protocol.http.sampler.HTTPSamplerFull=org.apache.jmeter.protocol.http.sampler.HTTPSampler +org.apache.jmeter.control.gui.RecordController=org.apache.jmeter.protocol.http.control.gui.RecordController + +org.apache.jmeter.timers.gui.ConstantThroughputTimerGui=org.apache.jmeter.testbeans.gui.TestBeanGUI +org.apache.jmeter.timers.ConstantThroughputTimer/ConstantThroughputTimer.throughput=throughput + +org.apache.jmeter.protocol.jdbc.control.gui.JdbcTestSampleGui=org.apache.jmeter.testbeans.gui.TestBeanGUI +org.apache.jmeter.protocol.jdbc.sampler.JDBCSampler/JDBCSampler.query=query +#org.apache.jmeter.protocol.jdbc.sampler.JDBCSampler.JDBCSampler.dataSource/NULL= + +# Convert DBconfig +org.apache.jmeter.protocol.jdbc.config.gui.DbConfigGui=org.apache.jmeter.testbeans.gui.TestBeanGUI +org.apache.jmeter.config.ConfigTestElement|org.apache.jmeter.protocol.jdbc.config.gui.DbConfigGui=org.apache.jmeter.protocol.jdbc.config.DataSourceElement +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.url=dbUrl +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.driver=driver +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.query=query +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/ConfigTestElement.username=username +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/ConfigTestElement.password=password + +# Convert PoolConfig +org.apache.jmeter.protocol.jdbc.config.gui.PoolConfigGui=org.apache.jmeter.testbeans.gui.TestBeanGUI +org.apache.jmeter.config.ConfigTestElement|org.apache.jmeter.protocol.jdbc.config.gui.PoolConfigGui=org.apache.jmeter.protocol.jdbc.config.DataSourceElement +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.connections= +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.connPoolClass= +org.apache.jmeter.protocol.jdbc.config.DataSourceElement/JDBCSampler.maxuse=poolMax + +# SQL Config +org.apache.jmeter.config.ConfigTestElement/JDBCSampler.query=query +org.apache.jmeter.protocol.http.control.Header/TestElement.name=Header.name + +# Upgrade AccessLogSampler +org.apache.jmeter.protocol.http.control.gui.AccessLogSamplerGui=org.apache.jmeter.testbeans.gui.TestBeanGUI +org.apache.jmeter.protocol.http.sampler.AccessLogSampler/AccessLogSampler.log_file=logFile +org.apache.jmeter.protocol.http.sampler.AccessLogSampler/HTTPSampler.port=portString +#Is the following used now? +#org.apache.jmeter.protocol.http.sampler.AccessLogSampler/AccessLogSampler.generator_class_name= +#Looks to be a new field +#filterClassName +org.apache.jmeter.protocol.http.sampler.AccessLogSampler/HTTPSampler.domain=domain +org.apache.jmeter.protocol.http.sampler.AccessLogSampler/AccessLogSampler.parser_class_name=parserClassName +org.apache.jmeter.protocol.http.sampler.AccessLogSampler/HTTPSampler.image_parser=imageParsing + +# Renamed class +org.apache.jmeter.protocol.jms.control.gui.JMSConfigGui=org.apache.jmeter.protocol.jms.control.gui.JMSSamplerGui + +# These classes have been deleted; there's no defined replacement +org.apache.jmeter.protocol.jdbc.config.gui.SqlConfigGui=org.apache.jmeter.config.gui.ObsoleteGui +org.apache.jmeter.protocol.jms.control.gui.JndiDefaultsGui=org.apache.jmeter.config.gui.ObsoleteGui +# Should probably map to something other than ObsoleteGui... +org.apache.jmeter.threads.ReflectionThreadGroup=org.apache.jmeter.config.gui.ObsoleteGui + +# Convert BSFSamplerGui +org.apache.jmeter.protocol.java.control.gui.BSFSamplerGui=org.apache.jmeter.testbeans.gui.TestBeanGUI +org.apache.jmeter.protocol.java.sampler.BSFSampler/BSFSampler.filename=filename +org.apache.jmeter.protocol.java.sampler.BSFSampler/BSFSampler.language=scriptLanguage +org.apache.jmeter.protocol.java.sampler.BSFSampler/BSFSampler.parameters=parameters +org.apache.jmeter.protocol.java.sampler.BSFSampler/BSFSampler.query=script + +# Obsolete Http user Parameters modifier test element +# Note: ConfigTestElement is the test element associated with ObsoleteGui +org.apache.jmeter.protocol.http.modifier.UserParameterModifier=org.apache.jmeter.config.ConfigTestElement +org.apache.jmeter.protocol.http.modifier.gui.UserParameterModifierGui=org.apache.jmeter.config.gui.ObsoleteGui + +# Obsolete Graph Full Results listener +org.apache.jmeter.visualizers.GraphAccumVisualizer=org.apache.jmeter.config.gui.ObsoleteGui +# removed in 3.0, class was deleted in r1722757 +org.apache.jmeter.protocol.http.sampler.WebServiceSampler=org.apache.jmeter.config.ConfigTestElement +# removed in 3.0, class was deleted in r1722757 +org.apache.jmeter.protocol.http.control.gui.WebServiceSamplerGui=org.apache.jmeter.config.gui.ObsoleteGui +# removed in 3.0, class was deleted in r1722757 +org.apache.jmeter.protocol.http.modifier.ParamModifier=org.apache.jmeter.config.ConfigTestElement +# removed in 3.0, class was deleted in r1722962 +org.apache.jmeter.protocol.http.modifier.ParamMask=org.apache.jmeter.config.ConfigTestElement +# removed in 3.0, class was deleted in r1722757 +org.apache.jmeter.protocol.http.modifier.gui.ParamModifierGui=org.apache.jmeter.config.gui.ObsoleteGui + +# removed in 3.1, class was deleted in r1774947 +org.apache.jmeter.visualizers.SplineVisualizer=org.apache.jmeter.config.gui.ObsoleteGui +# removed in 3.1 class was deleted in r1763837 +org.apache.jmeter.visualizers.DistributionGraphVisualizer=org.apache.jmeter.config.gui.ObsoleteGui + +# removed in 3.2 class was deleted in r1771608 +org.apache.jmeter.visualizers.MonitorStats=org.apache.jmeter.config.ConfigTestElement +org.apache.jmeter.visualizers.MonitorHealthVisualizer=org.apache.jmeter.config.gui.ObsoleteGui + +# removed in 3.2 class was deleted in r1783280 +org.apache.jmeter.protocol.http.sampler.HTTPSampler2=org.apache.jmeter.config.ConfigTestElement +org.apache.jmeter.protocol.http.sampler.SoapSampler=org.apache.jmeter.config.ConfigTestElement +org.apache.jmeter.protocol.http.control.gui.SoapSamplerGui=org.apache.jmeter.config.gui.ObsoleteGui \ No newline at end of file diff --git a/tools/jmeter/src/main/resources/jmeter.properties b/tools/jmeter/src/main/resources/jmeter.properties new file mode 100644 index 0000000000..87a42d2d91 --- /dev/null +++ b/tools/jmeter/src/main/resources/jmeter.properties @@ -0,0 +1,1242 @@ +################################################################################ +# Apache JMeter Property file +################################################################################ + +## Licensed to the Apache Software Foundation (ASF) under one or more +## contributor license agreements. See the NOTICE file distributed with +## this work for additional information regarding copyright ownership. +## The ASF licenses this file to You under the Apache License, Version 2.0 +## (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. + +################################################################################ +# +# THIS FILE SHOULD NOT BE MODIFIED +# +# This avoids having to re-apply the modifications when upgrading JMeter +# Instead only user.properties should be modified: +# 1/ copy the property you want to modify to user.properties from jmeter.properties +# 2/ Change its value there +# +################################################################################ + +# JMeter properties are described in the file +# http://jmeter.apache.org/usermanual/properties_reference.html +# A local copy can be found in +# printable_docs/usermanual/properties_reference.html + +#Preferred GUI language. Comment out to use the JVM default locale's language. +#language=en + + +# Additional locale(s) to add to the displayed list. +# The current default list is: en, fr, de, no, es, tr, ja, zh_CN, zh_TW, pl, pt_BR +# [see JMeterMenuBar#makeLanguageMenu()] +# The entries are a comma-separated list of language names +#locales.add=zu + + +#--------------------------------------------------------------------------- +# XML Parser +#--------------------------------------------------------------------------- + +# Path to a Properties file containing Namespace mapping in the form +# prefix=Namespace +# Example: +# ns=http://biz.aol.com/schema/2006-12-18 +#xpath.namespace.config= + +#--------------------------------------------------------------------------- +# SSL configuration +#--------------------------------------------------------------------------- + +## SSL System properties are now in system.properties + +# JMeter no longer converts javax.xxx property entries in this file into System properties. +# These must now be defined in the system.properties file or on the command-line. +# The system.properties file gives more flexibility. + +# By default, SSL session contexts are now created per-thread, rather than being shared. +# The original behaviour can be enabled by setting the JMeter property to true +#https.sessioncontext.shared=false + +# Be aware that https default protocol may vary depending on the version of JVM +# See https://blogs.oracle.com/java-platform-group/entry/diagnosing_tls_ssl_and_https +# See https://bz.apache.org/bugzilla/show_bug.cgi?id=58236 +# Default HTTPS protocol level: +#https.default.protocol=TLS +# This may need to be changed here (or in user.properties) to: +#https.default.protocol=SSLv3 + +# List of protocols to enable. You may have to select only a subset if you find issues with target server. +# This is needed when server does not support Socket version negotiation, this can lead to: +# javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated +# java.net.SocketException: Connection reset +# see https://bz.apache.org/bugzilla/show_bug.cgi?id=54759 +#https.socket.protocols=SSLv2Hello SSLv3 TLSv1 + +# Control if we allow reuse of cached SSL context between iterations +# set the value to 'false' to reset the SSL context each iteration +#https.use.cached.ssl.context=true + +# Start and end index to be used with keystores with many entries +# The default is to use entry 0, i.e. the first +#https.keyStoreStartIndex=0 +#https.keyStoreEndIndex=0 + +#--------------------------------------------------------------------------- +# Look and Feel configuration +#--------------------------------------------------------------------------- + +#Classname of the Swing default UI +# +# The LAF classnames that are available are now displayed as ToolTip text +# when hovering over the Options/Look and Feel selection list. +# +# You can either use a full class name, as shown below, +# or one of the strings "System" or "CrossPlatform" which means +# JMeter will use the corresponding string returned by UIManager.getLookAndFeelClassName() + +# LAF can be overridden by os.name (lowercased, spaces replaced by '_') +# Sample os.name LAF: +#jmeter.laf.windows_xp=javax.swing.plaf.metal.MetalLookAndFeel + +# Failing that, the OS family = os.name, but only up to first space: +# Sample OS family LAF: +#jmeter.laf.windows=com.sun.java.swing.plaf.windows.WindowsLookAndFeel + +# Mac apparently looks better with the System LAF +jmeter.laf.mac=System + +# Failing that, the JMeter default laf can be defined: +#jmeter.laf=System + +# If none of the above jmeter.laf properties are defined, JMeter uses the CrossPlatform LAF. +# This is because the CrossPlatform LAF generally looks better than the System LAF. +# See https://bz.apache.org/bugzilla/show_bug.cgi?id=52026 for details +# N.B. the laf can be defined in user.properties. + +# LoggerPanel display +# default to false +#jmeter.loggerpanel.display=false + +# Enable LogViewer Panel to receive log event even if closed +# Enabled since 2.12 +# Note this has some impact on performances, but as GUI mode must +# not be used for Load Test it is acceptable +#jmeter.loggerpanel.enable_when_closed=true + +# Max lines kept in LoggerPanel, default to 1000 chars +# 0 means no limit +#jmeter.loggerpanel.maxlength=1000 + +# Interval period in ms to process the queue of events of the listeners +#jmeter.gui.refresh_period=500 + +# HiDPI mode (default: false) +# Activate a 'pseudo'-hidpi mode. Allows to increase size of some UI elements +# which are not correctly managed by JVM with high resolution screens in Linux or Windows +#jmeter.hidpi.mode=false +# To enable pseudo-hidpi mode change to true +#jmeter.hidpi.mode=true +# HiDPI scale factor +#jmeter.hidpi.scale.factor=1.0 +# Suggested value for HiDPI +#jmeter.hidpi.scale.factor=2.0 + +# Toolbar display +# Toolbar icon definitions +#jmeter.toolbar.icons=org/apache/jmeter/images/toolbar/icons-toolbar.properties +# Toolbar list +#jmeter.toolbar=new,open,close,save,save_as_testplan,|,cut,copy,paste,|,expand,collapse,toggle,|,test_start,test_stop,test_shutdown,|,test_start_remote_all,test_stop_remote_all,test_shutdown_remote_all,|,test_clear,test_clear_all,|,search,search_reset,|,function_helper,help +# Toolbar icons default size: 22x22. Available sizes are: 22x22, 32x32, 48x48 +#jmeter.toolbar.icons.size=22x22 +# Suggested value for HiDPI +#jmeter.toolbar.icons.size=48x48 + +# Icon definitions +# default: +#jmeter.icons=org/apache/jmeter/images/icon.properties +# alternate: +#jmeter.icons=org/apache/jmeter/images/icon_1.properties +# Historical icon set (deprecated) +#jmeter.icons=org/apache/jmeter/images/icon_old.properties + +# Tree icons default size: 19x19. Available sizes are: 19x19, 24x24, 32x32, 48x48 +# Useful for HiDPI display (see below) +#jmeter.tree.icons.size=19x19 +# Suggested value for HiDPI screen like 3200x1800: +#jmeter.tree.icons.size=32x32 + +#Components to not display in JMeter GUI (GUI class name or static label) +# These elements are deprecated and will be removed in next version: +# MongoDB Script, MongoDB Source Config, Monitor Results +# BSF Elements +not_in_menu=org.apache.jmeter.protocol.mongodb.sampler.MongoScriptSampler,org.apache.jmeter.protocol.mongodb.config.MongoSourceElement,\ + org.apache.jmeter.timers.BSFTimer,org.apache.jmeter.modifiers.BSFPreProcessor,org.apache.jmeter.extractor.BSFPostProcessor,org.apache.jmeter.assertions.BSFAssertion,\ + org.apache.jmeter.visualizers.BSFListener,org.apache.jmeter.protocol.java.sampler.BSFSampler,\ + org.apache.jmeter.protocol.http.control.gui.SoapSamplerGui + +# Number of items in undo history +# Feature is disabled by default (0) due to known and not fixed bugs: +# https://bz.apache.org/bugzilla/show_bug.cgi?id=57043 +# https://bz.apache.org/bugzilla/show_bug.cgi?id=57039 +# https://bz.apache.org/bugzilla/show_bug.cgi?id=57040 +# Set it to a number > 0 (25 can be a good default) +# The bigger it is, the more it consumes memory +#undo.history.size=0 + +# Hotkeys to add JMeter components, will add elements when you press Ctrl+0 .. Ctrl+9 (Command+0 .. Command+9 on Mac) +gui.quick_0=ThreadGroupGui +gui.quick_1=HttpTestSampleGui +gui.quick_2=RegexExtractorGui +gui.quick_3=AssertionGui +gui.quick_4=ConstantTimerGui +gui.quick_5=TestActionGui +gui.quick_6=JSR223PostProcessor +gui.quick_7=JSR223PreProcessor +gui.quick_8=DebugSampler +gui.quick_9=ViewResultsFullVisualizer + + +#--------------------------------------------------------------------------- +# JMX Backup configuration +#--------------------------------------------------------------------------- +#Enable auto backups of the .jmx file when a test plan is saved. +#When enabled, before the .jmx is saved, it will be backed up to the directory pointed +#by the jmeter.gui.action.save.backup_directory property (see below). Backup file names are built +#after the jmx file being saved. For example, saving test-plan.jmx will create a test-plan-000012.jmx +#in the backup directory provided that the last created backup file is test-plan-000011.jmx. +#Default value is true indicating that auto backups are enabled +#jmeter.gui.action.save.backup_on_save=true + +#Set the backup directory path where JMX backups will be created upon save in the GUI. +#If not set (what it defaults to) then backup files will be created in +#a sub-directory of the JMeter base installation. The default directory is ${JMETER_HOME}/backups +#If set and the directory does not exist, it will be created. +#jmeter.gui.action.save.backup_directory= + +#Set the maximum time (in hours) that backup files should be preserved since the save time. +#By default no expiration time is set which means we keep backups for ever. +#jmeter.gui.action.save.keep_backup_max_hours=0 + +#Set the maximum number of backup files that should be preserved. By default 10 backups will be preserved. +#Setting this to zero will cause the backups to not being deleted (unless keep_backup_max_hours is set to a non zero value) +#jmeter.gui.action.save.keep_backup_max_count=10 + + +#--------------------------------------------------------------------------- +# Remote hosts and RMI configuration +#--------------------------------------------------------------------------- + +# Remote Hosts - comma delimited +remote_hosts=127.0.0.1 +#remote_hosts=localhost:1099,localhost:2010 + +# RMI port to be used by the server (must start rmiregistry with same port) +#server_port=1099 + +# To change the port to (say) 1234: +# On the server(s) +# - set server_port=1234 +# - start rmiregistry with port 1234 +# On Windows this can be done by: +# SET SERVER_PORT=1234 +# JMETER-SERVER +# +# On Unix: +# SERVER_PORT=1234 jmeter-server +# +# On the client: +# - set remote_hosts=server:1234 + +# Parameter that controls the RMI port used by the RemoteSampleListenerImpl (The Controler) +# Default value is 0 which means port is randomly assigned +# You may need to open Firewall port on the Controller machine +#client.rmi.localport=0 + +# When distributed test is starting, there may be several attempts to initialize +# remote engines. By default, only single try is made. Increase following property +# to make it retry for additional times +#client.tries=1 + +# If there is initialization retries, following property sets delay between attempts +#client.retries_delay=5000 + +# When all initialization tries was made, test will fail if some remote engines are failed +# Set following property to true to ignore failed nodes and proceed with test +#client.continue_on_fail=false + +# To change the default port (1099) used to access the server: +#server.rmi.port=1234 + +# To use a specific port for the JMeter server engine, define +# the following property before starting the server: +#server.rmi.localport=4000 + +# From JMeter 2.3.1, the jmeter server creates the RMI registry as part of the server process. +# To stop the server creating the RMI registry: +#server.rmi.create=false + +# From JMeter 2.3.1, define the following property to cause JMeter to exit after the first test +#server.exitaftertest=true + +#--------------------------------------------------------------------------- +# Include Controller +#--------------------------------------------------------------------------- + +# Prefix used by IncludeController when building file name +#includecontroller.prefix= + +#--------------------------------------------------------------------------- +# HTTP Java configuration +#--------------------------------------------------------------------------- + +# Number of connection retries performed by HTTP Java sampler before giving up +# 0 means no retry since version 3.0 +#http.java.sampler.retries=0 + +#--------------------------------------------------------------------------- +# Following properties apply to Apache HttpClient +#--------------------------------------------------------------------------- + +# set the socket timeout (or use the parameter http.socket.timeout) +# for AJP Sampler implementation. +# Value is in milliseconds +#httpclient.timeout=0 +# 0 == no timeout + +# Set the http version (defaults to 1.1) +#httpclient.version=1.1 (or use the parameter http.protocol.version) + +# Define characters per second > 0 to emulate slow connections +#httpclient.socket.http.cps=0 +#httpclient.socket.https.cps=0 + +#Enable loopback protocol +#httpclient.loopback=true + +# Define the local host address to be used for multi-homed hosts +#httpclient.localaddress=1.2.3.4 + +#--------------------------------------------------------------------------- +# AuthManager Kerberos configuration +#--------------------------------------------------------------------------- + +# AuthManager Kerberos configuration +# Name of application module used in jaas.conf +#kerberos_jaas_application=JMeter + +# Should ports be stripped from urls before constructing SPNs +# for SPNEGO authentication +#kerberos.spnego.strip_port=true + +#--------------------------------------------------------------------------- +# Apache HttpComponents HTTPClient configuration (HTTPClient4) +#--------------------------------------------------------------------------- + +# define a properties file for overriding Apache HttpClient parameters +# Uncomment this line if you put anything in hc.parameters file +#hc.parameters.file=hc.parameters + +# Preemptively send Authorization Header when BASIC auth is used +#httpclient4.auth.preemptive=true + +# Number of retries to attempt (default 0) +#httpclient4.retrycount=0 + +# true if it's OK to retry requests that have been sent +# This will retry Idempotent and non Idempotent requests +# This should usually be false, but it can be useful +# when testing against some Load Balancers like Amazon ELB +#httpclient4.request_sent_retry_enabled=false + +# Idle connection timeout (Milliseconds) to apply if the server does not send +# Keep-Alive headers (default 0) +# Set this > 0 to compensate for servers that don't send a Keep-Alive header +# If <= 0, idle timeout will only apply if the server sends a Keep-Alive header +#httpclient4.idletimeout=0 + +# Check connections if the elapsed time (Milliseconds) since the last +# use of the connection exceed this value +#httpclient4.validate_after_inactivity=1700 + +# TTL (in Milliseconds) represents an absolute value. +# No matter what, the connection will not be re-used beyond its TTL. +#httpclient4.time_to_live=2000 + +# Max size in bytes of PUT body to retain in result sampler. Bigger results will be clipped. +#httpclient4.max_body_retain_size=32768 + +#--------------------------------------------------------------------------- +# HTTP Cache Manager configuration +#--------------------------------------------------------------------------- +# +# Space or comma separated list of methods that can be cached +#cacheable_methods=GET +# N.B. This property is currently a temporary solution for Bug 56162 + +# Since 2.12, JMeter does not create anymore a Sample Result with 204 response +# code for a resource found in cache which is inline with what browser do. +#cache_manager.cached_resource_mode=RETURN_NO_SAMPLE + +# You can choose between 3 modes: +# RETURN_NO_SAMPLE (default) +# RETURN_200_CACHE +# RETURN_CUSTOM_STATUS + +# Those mode have the following behaviours: +# RETURN_NO_SAMPLE : this mode returns no Sample Result, it has no additional configuration +# RETURN_200_CACHE : this mode will return Sample Result with response code to 200 and response message to "(ex cache)", you can modify response message by setting +# RETURN_200_CACHE.message=(ex cache) +# RETURN_CUSTOM_STATUS : This mode lets you select what response code and message you want to return, if you use this mode you need to set those properties +# RETURN_CUSTOM_STATUS.code= +# RETURN_CUSTOM_STATUS.message= + +#--------------------------------------------------------------------------- +# Results file configuration +#--------------------------------------------------------------------------- + +# This section helps determine how result data will be saved. +# The commented out values are the defaults. + +# legitimate values: xml, csv, db. Only xml and csv are currently supported. +#jmeter.save.saveservice.output_format=csv + + +# true when field should be saved; false otherwise + +# assertion_results_failure_message only affects CSV output +#jmeter.save.saveservice.assertion_results_failure_message=true +# +# legitimate values: none, first, all +#jmeter.save.saveservice.assertion_results=none +# +#jmeter.save.saveservice.data_type=true +#jmeter.save.saveservice.label=true +#jmeter.save.saveservice.response_code=true +# response_data is not currently supported for CSV output +#jmeter.save.saveservice.response_data=false +# Save ResponseData for failed samples +#jmeter.save.saveservice.response_data.on_error=false +#jmeter.save.saveservice.response_message=true +#jmeter.save.saveservice.successful=true +#jmeter.save.saveservice.thread_name=true +#jmeter.save.saveservice.time=true +#jmeter.save.saveservice.subresults=true +#jmeter.save.saveservice.assertions=true +#jmeter.save.saveservice.latency=true +# Only available with HttpClient4 +#jmeter.save.saveservice.connect_time=true +#jmeter.save.saveservice.samplerData=false +#jmeter.save.saveservice.responseHeaders=false +#jmeter.save.saveservice.requestHeaders=false +#jmeter.save.saveservice.encoding=false +#jmeter.save.saveservice.bytes=true +# Only available with HttpClient4 +#jmeter.save.saveservice.sent_bytes=true +#jmeter.save.saveservice.url=false +#jmeter.save.saveservice.filename=false +#jmeter.save.saveservice.hostname=false +#jmeter.save.saveservice.thread_counts=true +#jmeter.save.saveservice.sample_count=false +#jmeter.save.saveservice.idle_time=true + +# Timestamp format - this only affects CSV output files +# legitimate values: none, ms, or a format suitable for SimpleDateFormat +#jmeter.save.saveservice.timestamp_format=ms +#jmeter.save.saveservice.timestamp_format=yyyy/MM/dd HH:mm:ss.SSS + +# For use with Comma-separated value (CSV) files or other formats +# where the fields' values are separated by specified delimiters. +# Default: +#jmeter.save.saveservice.default_delimiter=, +# For TAB, since JMeter 2.3 one can use: +#jmeter.save.saveservice.default_delimiter=\t + +# Only applies to CSV format files: +# Print field names as first line in CSV +#jmeter.save.saveservice.print_field_names=true + +# Optional list of JMeter variable names whose values are to be saved in the result data files. +# Use commas to separate the names. For example: +#sample_variables=SESSION_ID,REFERENCE +# N.B. The current implementation saves the values in XML as attributes, +# so the names must be valid XML names. +# Versions of JMeter after 2.3.2 send the variable to all servers +# to ensure that the correct data is available at the client. + +# Optional xml processing instruction for line 2 of the file: +# Example: +#jmeter.save.saveservice.xml_pi= +# Default value: +#jmeter.save.saveservice.xml_pi= + +# Prefix used to identify filenames that are relative to the current base +#jmeter.save.saveservice.base_prefix=~/ + +# AutoFlush on each line written in XML or CSV output +# Setting this to true will result in less test results data loss in case of Crash +# but with impact on performances, particularly for intensive tests (low or no pauses) +# Since JMeter 2.10, this is false by default +#jmeter.save.saveservice.autoflush=false + +#--------------------------------------------------------------------------- +# Settings that affect SampleResults +#--------------------------------------------------------------------------- + +# Save the start time stamp instead of the end +# This also affects the timestamp stored in result files +sampleresult.timestamp.start=true + +# Whether to use System.nanoTime() - otherwise only use System.currentTimeMillis() +#sampleresult.useNanoTime=true + +# Use a background thread to calculate the nanoTime offset +# Set this to <= 0 to disable the background thread +#sampleresult.nanoThreadSleep=5000 + +#--------------------------------------------------------------------------- +# Upgrade property +#--------------------------------------------------------------------------- + +# File that holds a record of name changes for backward compatibility issues +upgrade_properties=/bin/upgrade.properties + +#--------------------------------------------------------------------------- +# JMeter Test Script recorder configuration +# +# N.B. The element was originally called the Proxy recorder, which is why the +# properties have the prefix "proxy". +#--------------------------------------------------------------------------- + +# If the recorder detects a gap of at least 5s (default) between HTTP requests, +# it assumes that the user has clicked a new URL +#proxy.pause=5000 + +# Add numeric prefix to Sampler names (default true) +#proxy.number.requests=true + +# List of URL patterns that will be added to URL Patterns to exclude +# Separate multiple lines with ; +#proxy.excludes.suggested=.*\\.(bmp|css|js|gif|ico|jpe?g|png|swf|woff|woff2) + +# Change the default HTTP Sampler (currently HttpClient4) +# Java: +#jmeter.httpsampler=HTTPSampler +#or +#jmeter.httpsampler=Java +# +# HttpClient4.x +#jmeter.httpsampler=HttpClient4 + +# By default JMeter tries to be more lenient with RFC2616 redirects and allows +# relative paths. +# If you want to test strict conformance, set this value to true +# When the property is true, JMeter follows http://tools.ietf.org/html/rfc3986#section-5.2 +#jmeter.httpclient.strict_rfc2616=false + +# Default content-type include filter to use +#proxy.content_type_include=text/html|text/plain|text/xml +# Default content-type exclude filter to use +#proxy.content_type_exclude=image/.*|text/css|application/.* + +# Default headers to remove from Header Manager elements +# (Cookie and Authorization are always removed) +#proxy.headers.remove=If-Modified-Since,If-None-Match,Host + +# Binary content-type handling +# These content-types will be handled by saving the request in a file: +#proxy.binary.types=application/x-amf,application/x-java-serialized-object +# The files will be saved in this directory: +#proxy.binary.directory=user.dir +# The files will be created with this file filesuffix: +#proxy.binary.filesuffix=.binary + +#--------------------------------------------------------------------------- +# Test Script Recorder certificate configuration +#--------------------------------------------------------------------------- + +#proxy.cert.directory= +#proxy.cert.file=proxyserver.jks +#proxy.cert.type=JKS +#proxy.cert.keystorepass=password +#proxy.cert.keypassword=password +#proxy.cert.factory=SunX509 +# define this property if you wish to use your own keystore +#proxy.cert.alias= +# The default validity for certificates created by JMeter +#proxy.cert.validity=7 +# Use dynamic key generation (if supported by JMeter/JVM) +# If false, will revert to using a single key with no certificate +#proxy.cert.dynamic_keys=true + +#--------------------------------------------------------------------------- +# Test Script Recorder miscellaneous configuration +#--------------------------------------------------------------------------- + +# Whether to attempt disabling of samples that resulted from redirects +# where the generated samples use auto-redirection +#proxy.redirect.disabling=true + +# SSL configuration +#proxy.ssl.protocol=TLS + +#--------------------------------------------------------------------------- +# JMeter Proxy configuration +#--------------------------------------------------------------------------- +# use command-line flags for user-name and password +#http.proxyDomain=NTLM domain, if required by HTTPClient sampler + +#--------------------------------------------------------------------------- +# HTTPSampleResponse Parser configuration +#--------------------------------------------------------------------------- + +# Space-separated list of parser groups +HTTPResponse.parsers=htmlParser wmlParser cssParser +# for each parser, there should be a parser.types and a parser.className property + +# CSS Parser based on ph-css +cssParser.className=org.apache.jmeter.protocol.http.parser.CssParser +cssParser.types=text/css + +# CSS parser LRU cache size +# This cache stores the URLs found in a CSS to avoid continuously parsing the CSS +# By default the cache size is 400 +# It can be disabled by setting its value to 0 +#css.parser.cache.size=400 + +# Let the CSS Parser ingore all css errors +#css.parser.ignore_all_css_errors=true + +#--------------------------------------------------------------------------- +# HTML Parser configuration +#--------------------------------------------------------------------------- + +# Define the HTML parser to be used. +# Default parser: +# This new parser (since 2.10) should perform better than all others +# see https://bz.apache.org/bugzilla/show_bug.cgi?id=55632 +# Do not comment this property +htmlParser.className=org.apache.jmeter.protocol.http.parser.LagartoBasedHtmlParser + +# Other parsers: +# Default parser before 2.10 +#htmlParser.className=org.apache.jmeter.protocol.http.parser.JTidyHTMLParser +# Note that Regexp extractor may detect references that have been commented out. +# In many cases it will work OK, but you should be aware that it may generate +# additional references. +#htmlParser.className=org.apache.jmeter.protocol.http.parser.RegexpHTMLParser +# This parser is based on JSoup, it should be the most accurate but less performant +# than LagartoBasedHtmlParser +#htmlParser.className=org.apache.jmeter.protocol.http.parser.JsoupBasedHtmlParser + +#Used by HTTPSamplerBase to associate htmlParser with content types below +htmlParser.types=text/html application/xhtml+xml application/xml text/xml + +#--------------------------------------------------------------------------- +# WML Parser configuration +#--------------------------------------------------------------------------- + +wmlParser.className=org.apache.jmeter.protocol.http.parser.RegexpHTMLParser + +#Used by HTTPSamplerBase to associate wmlParser with content types below +wmlParser.types=text/vnd.wap.wml + +#--------------------------------------------------------------------------- +# Remote batching configuration +#--------------------------------------------------------------------------- +# How is Sample sender implementations configured: +# - true (default) means client configuration will be used +# - false means server configuration will be used +#sample_sender_client_configured=true + +# By default when Stripping modes are used JMeter since 3.1 will strip +# response even for SampleResults in error. +# If you want to revert to previous behaviour (no stripping of Responses in error) +# set this property to false +#sample_sender_strip_also_on_error=true + +# Remote batching support +# Since JMeter 2.9, default is MODE_STRIPPED_BATCH, which returns samples in +# batch mode (every 100 samples or every minute by default) +# Note also that MODE_STRIPPED_BATCH strips response data from SampleResult, so if you need it change to +# another mode +# Hold retains samples until end of test (may need lots of memory) +# Batch returns samples in batches +# Statistical returns sample summary statistics +# hold_samples was originally defined as a separate property, +# but can now also be defined using mode=Hold +# mode can also be the class name of an implementation of org.apache.jmeter.samplers.SampleSender +#mode=Standard +#mode=Batch +#mode=Hold +#mode=Statistical +#Set to true to key statistical samples on threadName rather than threadGroup +#key_on_threadname=false +#mode=Stripped +#mode=StrippedBatch +#mode=org.example.load.MySampleSender +# +#num_sample_threshold=100 +# Value is in milliseconds +#time_threshold=60000 +# +# Asynchronous sender; uses a queue and background worker process to return the samples +#mode=Asynch +# default queue size +#asynch.batch.queue.size=100 +# Same as Asynch but strips response data from SampleResult +#mode=StrippedAsynch +# +# DiskStore: as for Hold mode, but serialises the samples to disk, rather than saving in memory +#mode=DiskStore +# Same as DiskStore but strips response data from SampleResult +#mode=StrippedDiskStore +# Note: the mode is currently resolved on the client; +# other properties (e.g. time_threshold) are resolved on the server. + +#--------------------------------------------------------------------------- +# JDBC Request configuration +#--------------------------------------------------------------------------- + +# String used to indicate a null value +#jdbcsampler.nullmarker=]NULL[ +# +# Max size of BLOBs and CLOBs to store in JDBC sampler. Result will be cut off +#jdbcsampler.max_retain_result_size=65536 + +# Database validation query +# based in https://stackoverflow.com/questions/10684244/dbcp-validationquery-for-different-databases list +jdbc.config.check.query=select 1 from INFORMATION_SCHEMA.SYSTEM_USERS|select 1 from dual|select 1 from sysibm.sysdummy1|select 1|select 1 from rdb$database +jdbc.config.jdbc.driver.class=com.mysql.jdbc.Driver|org.postgresql.Driver|oracle.jdbc.OracleDriver|com.ingres.jdbc.IngresDriver|com.microsoft.sqlserver.jdbc.SQLServerDriver|com.microsoft.jdbc.sqlserver.SQLServerDriver|org.apache.derby.jdbc.ClientDriver|org.hsqldb.jdbc.JDBCDriver|com.ibm.db2.jcc.DB2Driver|org.apache.derby.jdbc.ClientDriver|org.h2.Driver|org.firebirdsql.jdbc.FBDrivery|org.mariadb.jdbc.Driver|org.sqlite.JDBC|net.sourceforge.jtds.jdbc.Driver + +#--------------------------------------------------------------------------- +# OS Process Sampler configuration +#--------------------------------------------------------------------------- +# Polling to see if process has finished its work, used when a timeout is configured on sampler +#os_sampler.poll_for_timeout=100 + +#--------------------------------------------------------------------------- +# TCP Sampler configuration +#--------------------------------------------------------------------------- + +# The default handler class +#tcp.handler=TCPClientImpl +# +# eolByte = byte value for end of line +# set this to a value outside the range -128 to +127 to skip eol checking +#tcp.eolByte=1000 +# +# TCP Charset, used by org.apache.jmeter.protocol.tcp.sampler.TCPClientImpl +# default to Platform defaults charset as returned by Charset.defaultCharset().name() +#tcp.charset= +# +# status.prefix and suffix = strings that enclose the status response code +#tcp.status.prefix=Status= +#tcp.status.suffix=. +# +# status.properties = property file to convert codes to messages +#tcp.status.properties=mytestfiles/tcpstatus.properties + +# The length prefix used by LengthPrefixedBinaryTCPClientImpl implementation +# defaults to 2 bytes. +#tcp.binarylength.prefix.length=2 + +#--------------------------------------------------------------------------- +# Summariser - Generate Summary Results - configuration (mainly applies to non-GUI mode) +#--------------------------------------------------------------------------- +# +# Comment the following property to disable the default non-GUI summariser +# [or change the value to rename it] +# (applies to non-GUI mode only) +summariser.name=summary +# +# interval between summaries (in seconds) default 30 seconds +#summariser.interval=30 +# +# Write messages to log file +#summariser.log=true +# +# Write messages to System.out +#summariser.out=true + +# Ignore SampleResults generated by TransactionControllers +# defaults to true +#summariser.ignore_transaction_controller_sample_result=true + + +#--------------------------------------------------------------------------- +# Aggregate Report and Aggregate Graph - configuration +#--------------------------------------------------------------------------- +# +# Percentiles to display in reports +# Can be float value between 0 and 100 +# First percentile to display, defaults to 90% +#aggregate_rpt_pct1=90 +# Second percentile to display, defaults to 95% +#aggregate_rpt_pct2=95 +# Second percentile to display, defaults to 99% +#aggregate_rpt_pct3=99 + +#--------------------------------------------------------------------------- +# BackendListener - configuration +#--------------------------------------------------------------------------- +# +# Backend metrics window mode (fixed=fixed-size window, timed=time boxed) +#backend_metrics_window_mode=fixed +# Backend metrics sliding window size for Percentiles, Min, Max +#backend_metrics_window=100 + +# Backend metrics sliding window size for Percentiles, Min, Max +# when backend_metrics_window_mode is timed +# Setting this value too high can lead to OOM +#backend_metrics_large_window=5000 + +######################## +# Graphite Backend +######################## +# Send interval in second +# Defaults to 1 second +#backend_graphite.send_interval=1 + +######################## +# Influx Backend +######################## + +# Send interval in second +# Defaults to 5 seconds +#backend_influxdb.send_interval=5 +#Influxdb timeouts +#backend_influxdb.connection_timeout=1000 +#backend_influxdb.socket_timeout=3000 +#backend_influxdb.connection_request_timeout=100 + +#--------------------------------------------------------------------------- +# BeanShell configuration +#--------------------------------------------------------------------------- + +# BeanShell Server properties +# +# Define the port number as non-zero to start the http server on that port +#beanshell.server.port=9000 +# The telnet server will be started on the next port + +# +# Define the server initialisation file +beanshell.server.file=../extras/startup.bsh + +# +# Define a file to be processed at startup +# This is processed using its own interpreter. +#beanshell.init.file= + +# +# Define the intialisation files for BeanShell Sampler, Function and other BeanShell elements +# N.B. Beanshell test elements do not share interpreters. +# Each element in each thread has its own interpreter. +# This is retained between samples. +#beanshell.sampler.init=BeanShellSampler.bshrc +#beanshell.function.init=BeanShellFunction.bshrc +#beanshell.assertion.init=BeanShellAssertion.bshrc +#beanshell.listener.init=etc +#beanshell.postprocessor.init=etc +#beanshell.preprocessor.init=etc +#beanshell.timer.init=etc + +# The file BeanShellListeners.bshrc contains sample definitions +# of Test and Thread Listeners. + +#--------------------------------------------------------------------------- +# Groovy function +#--------------------------------------------------------------------------- + +#Path to Groovy file containing utility functions to make available to __groovy function +#groovy.utilities= + +# Example +#groovy.utilities=bin/utility.groovy + +#--------------------------------------------------------------------------- +# MailerModel configuration +#--------------------------------------------------------------------------- + +# Number of successful samples before a message is sent +#mailer.successlimit=2 +# +# Number of failed samples before a message is sent +#mailer.failurelimit=2 + +#--------------------------------------------------------------------------- +# CSVRead configuration +#--------------------------------------------------------------------------- + +# CSVRead delimiter setting (default ",") +# Make sure that there are no trailing spaces or tabs after the delimiter +# characters, or these will be included in the list of valid delimiters +#csvread.delimiter=, +#csvread.delimiter=; +#csvread.delimiter=! +#csvread.delimiter=~ +# The following line has a tab after the = +#csvread.delimiter= + +#--------------------------------------------------------------------------- +# __time() function configuration +# +# The properties below can be used to redefine the default formats +#--------------------------------------------------------------------------- +#time.YMD=yyyyMMdd +#time.HMS=HHmmss +#time.YMDHMS=yyyyMMdd-HHmmss +#time.USER1= +#time.USER2= + +#--------------------------------------------------------------------------- +# CSV DataSet configuration +#--------------------------------------------------------------------------- + +# String to return at EOF (if recycle not used) +#csvdataset.eofstring= +#list in https://docs.oracle.com/javase/8/docs/technotes/guides/intl/encoding.doc.html +csvdataset.file.encoding_list=UTF-8|UTF-16|ISO-8859-15|US-ASCII + + +#--------------------------------------------------------------------------- +# LDAP Sampler configuration +#--------------------------------------------------------------------------- +# Maximum number of search results returned by a search that will be sorted +# to guarantee a stable ordering (if more results then this limit are returned +# then no sorting is done). Set to 0 to turn off all sorting, in which case +# "Equals" response assertions will be very likely to fail against search results. +# +#ldapsampler.max_sorted_results=1000 + +# Number of characters to log for each of three sections (starting matching section, diff section, +# ending matching section where not all sections will appear for all diffs) diff display when an Equals +# assertion fails. So a value of 100 means a maximum of 300 characters of diff text will be displayed +# (+ a number of extra characters like "..." and "[[["/"]]]" which are used to decorate it). +#assertion.equals_section_diff_len=100 +# test written out to log to signify start/end of diff delta +#assertion.equals_diff_delta_start=[[[ +#assertion.equals_diff_delta_end=]]] + +#--------------------------------------------------------------------------- +# Miscellaneous configuration +#--------------------------------------------------------------------------- +# Used to control what happens when you start a test and +# have listeners that could overwrite existing result files +# Possible values: +# ASK : Ask user +# APPEND : Append results to existing file +# DELETE : Delete existing file and start a new file +#resultcollector.action_if_file_exists=ASK + +# If defined, then start the mirror server on the port +#mirror.server.port=8081 + +# ORO PatternCacheLRU size +#oro.patterncache.size=1000 + +#TestBeanGui +# +#propertyEditorSearchPath=null + +# Turn expert mode on/off: expert mode will show expert-mode beans and properties +#jmeter.expertMode=true + +# Max size of bytes stored in memory per SampleResult +# Ensure you don't exceed max capacity of a Java Array and remember +# that the higher it is, the higher JMeter will consume heap +# Defaults to 0, which means no truncation +#httpsampler.max_bytes_to_store_per_request=0 + +# Max size of buffer in bytes used when reading responses +# Defaults to 64k +#httpsampler.max_buffer_size=66560 + +# Maximum redirects to follow in a single sequence (default 20) +#httpsampler.max_redirects=20 +# Maximum frame/iframe nesting depth (default 5) +#httpsampler.max_frame_depth=5 + +# Revert to BUG 51939 behaviour (no separate container for embedded resources) by setting the following false: +#httpsampler.separate.container=true + +# If embedded resources download fails due to missing resources or other reasons, if this property is true +# Parent sample will not be marked as failed +#httpsampler.ignore_failed_embedded_resources=false + +#keep alive time for the parallel download threads (in seconds) +#httpsampler.parallel_download_thread_keepalive_inseconds=60 + +# Don't keep the embedded resources response data : just keep the size and the md5 +# default to false +#httpsampler.embedded_resources_use_md5=false + +# List of extra HTTP methods that should be available in select box +#httpsampler.user_defined_methods=VERSION-CONTROL,REPORT,CHECKOUT,CHECKIN,UNCHECKOUT,MKWORKSPACE,UPDATE,LABEL,MERGE,BASELINE-CONTROL,MKACTIVITY + +# The encoding to be used if none is provided (default ISO-8859-1) +#sampleresult.default.encoding=ISO-8859-1 + +# Network response size calculation method +# Use real size: number of bytes for response body return by webserver +# (i.e. the network bytes received for response) +# if set to false, the (uncompressed) response data size will used (default before 2.5) +# Include headers: add the headers size in real size +#sampleresult.getbytes.body_real_size=true +#sampleresult.getbytes.headers_size=true + +# CookieManager behaviour - should cookies with null/empty values be deleted? +# Default is true. Use false to revert to original behaviour +#CookieManager.delete_null_cookies=true + +# CookieManager behaviour - should variable cookies be allowed? +# Default is true. Use false to revert to original behaviour +#CookieManager.allow_variable_cookies=true + +# CookieManager behaviour - should Cookies be stored as variables? +# Default is false +#CookieManager.save.cookies=false + +# CookieManager behaviour - prefix to add to cookie name before storing it as a variable +# Default is COOKIE_; to remove the prefix, define it as one or more spaces +#CookieManager.name.prefix= + +# CookieManager behaviour - check received cookies are valid before storing them? +# Default is true. Use false to revert to previous behaviour +#CookieManager.check.cookies=true + +# Netscape HTTP Cookie file +cookies=cookies + +# Ability to switch to Nashorn as default Javascript Engine used by IfController and __javaScript function +# JMeter works as following: +# - JDK >= 8 and javascript.use_rhino=false or not set : Nashorn +# - JDK >= 8 and javascript.use_rhino=true: Rhino +# If you want to use Rhino on JDK8, set this property to true +#javascript.use_rhino=false + +# Number of milliseconds to wait for a thread to stop +#jmeterengine.threadstop.wait=5000 + +#Whether to invoke System.exit(0) in server exit code after stopping RMI +#jmeterengine.remote.system.exit=false + +# Whether to call System.exit(1) on failure to stop threads in non-GUI mode. +# This only takes effect if the test was explicitly requested to stop. +# If this is disabled, it may be necessary to kill the JVM externally +#jmeterengine.stopfail.system.exit=true + +# Whether to force call System.exit(0) at end of test in non-GUI mode, even if +# there were no failures and the test was not explicitly asked to stop. +# Without this, the JVM may never exit if there are other threads spawned by +# the test which never exit. +#jmeterengine.force.system.exit=false + +# How long to pause (in ms) in the daemon thread before reporting that the JVM has failed to exit. +# If the value is <= 0, the JMeter does not start the daemon thread +#jmeter.exit.check.pause=2000 + +# If running non-GUI, then JMeter listens on the following port for a shutdown message. +# To disable, set the port to 1000 or less. +#jmeterengine.nongui.port=4445 +# +# If the initial port is busy, keep trying until this port is reached +# (to disable searching, set the value less than or equal to the .port property) +#jmeterengine.nongui.maxport=4455 + +# How often to check for shutdown during ramp-up (milliseconds) +#jmeterthread.rampup.granularity=1000 + +#Should JMeter expand the tree when loading a test plan? +# default value is false since JMeter 2.7 +#onload.expandtree=false + +#JSyntaxTextArea configuration +#jsyntaxtextarea.wrapstyleword=true +#jsyntaxtextarea.linewrap=true +#jsyntaxtextarea.codefolding=true +# Set 0 to disable undo feature in JSyntaxTextArea +#jsyntaxtextarea.maxundos=50 +# Change the font on the (JSyntax) Text Areas. (Useful for HiDPI screens) +#jsyntaxtextarea.font.family=Hack +#jsyntaxtextarea.font.size=14 + +# Set this to false to disable the use of JSyntaxTextArea for the Console Logger panel +#loggerpanel.usejsyntaxtext=true + +# Maximum size of HTML page that can be displayed; default=10 mbytes +# Set to 0 to disable the size check and display the whole response +#view.results.tree.max_size=10485760 + +# Order of Renderers in View Results Tree +# Note full class names should be used for non jmeter core renderers +# For JMeter core renderers, class names start with . and are automatically +# prefixed with org.apache.jmeter.visualizers +view.results.tree.renderers_order=.RenderAsText,.RenderAsRegexp,.RenderAsCssJQuery,.RenderAsXPath,org.apache.jmeter.extractor.json.render.RenderAsJsonRenderer,.RenderAsHTML,.RenderAsHTMLFormatted,.RenderAsHTMLWithEmbedded,.RenderAsDocument,.RenderAsJSON,.RenderAsXML + +# Maximum number of results in the results tree +# Set to 0 to store all results (might consume a lot of memory) +#view.results.tree.max_results=500 + +# Maximum size of Document that can be parsed by Tika engine; defaut=10 * 1024 * 1024 (10MB) +# Set to 0 to disable the size check +#document.max_size=0 + +#JMS options +# Enable the following property to stop JMS Point-to-Point Sampler from using +# the properties java.naming.security.[principal|credentials] when creating the queue connection +#JMSSampler.useSecurity.properties=false + +# Set the following value to true in order to skip the delete confirmation dialogue +#confirm.delete.skip=false + +# Used by JSR223 elements +# Size of compiled scripts cache +#jsr223.compiled_scripts_cache_size=100 + +#--------------------------------------------------------------------------- +# Classpath configuration +#--------------------------------------------------------------------------- + +# List of directories (separated by ;) to search for additional JMeter plugin classes, +# for example new GUI elements and samplers. +# Any jar file in such a directory will be automatically included, +# jar files in sub directories are ignored. +# The given value is in addition to any jars found in the lib/ext directory. +# Do not use this for utility or plugin dependency jars. +#search_paths=/app1/lib;/app2/lib + +# List of directories that JMeter will search for utility and plugin dependency classes. +# Use your platform path separator to separate multiple paths. +# Any jar file in such a directory will be automatically included, +# jar files in sub directories are ignored. +# The given value is in addition to any jars found in the lib directory. +# All entries will be added to the class path of the system class loader +# and also to the path of the JMeter internal loader. +# Paths with spaces may cause problems for the JVM +#user.classpath=../classes;../lib + +# List of directories (separated by ;) that JMeter will search for utility +# and plugin dependency classes. +# Any jar file in such a directory will be automatically included, +# jar files in sub directories are ignored. +# The given value is in addition to any jars found in the lib directory +# or given by the user.classpath property. +# All entries will be added to the path of the JMeter internal loader only. +# For plugin dependencies this property should be used instead of user.classpath. +#plugin_dependency_paths=../dependencies/lib;../app1/;../app2/ + +# Classpath finder +# ================ +# The classpath finder currently needs to load every single JMeter class to find +# the classes it needs. +# For non-GUI mode, it's only necessary to scan for Function classes, but all classes +# are still loaded. +# All current Function classes include ".function." in their name, +# and none include ".gui." in the name, so the number of unwanted classes loaded can be +# reduced by checking for these. However, if a valid function class name does not match +# these restrictions, it will not be loaded. If problems are encountered, then comment +# or change the following properties: +classfinder.functions.contain=.functions. +classfinder.functions.notContain=.gui. + + +#--------------------------------------------------------------------------- +# Additional property files to load +#--------------------------------------------------------------------------- + +# Should JMeter automatically load additional JMeter properties? +# File name to look for (comment to disable) +user.properties=user.properties + +# Should JMeter automatically load additional system properties? +# File name to look for (comment to disable) +system.properties=system.properties + +# Comma separated list of files that contain reference to templates and their description +# Path must be relative to JMeter root folder +#template.files=/bin/templates/templates.xml + + +#--------------------------------------------------------------------------- +# Thread Group Validation feature +#--------------------------------------------------------------------------- + +# Validation is the name of the feature used to rapidly validate a Thread Group runs fine +# Default implementation is org.apache.jmeter.gui.action.validation.TreeClonerForValidation +# It runs validation without timers, with 1 thread, 1 iteration and Startup Delay set to 0 +# You can implement your own policy that must extend org.apache.jmeter.engine.TreeCloner +# JMeter will instantiate it and use it to create the Tree used to run validation on Thread Group +#testplan_validation.tree_cloner_class=org.apache.jmeter.validation.ComponentTreeClonerForValidation + +# Number of threads to use to validate a Thread Group +#testplan_validation.nb_threads_per_thread_group=1 + +# Ignore BackendListener when validating the thread group of plan +#testplan_validation.ignore_backends=true + +# Ignore timers when validating the thread group of plan +#testplan_validation.ignore_timers=true + +# Number of iterations to use to validate a Thread Group +#testplan_validation.number_iterations=1 + +# Force throuput controllers that work in percentage mode to be a 100% +# Disabled by default +#testplan_validation.tpc_force_100_pct=false + +#--------------------------------------------------------------------------- +# Think Time configuration +#--------------------------------------------------------------------------- + +# +# Apply a factor on computed pauses by the following Timers: +# - Gaussian Random Timer +# - Uniform Random Timer +# - Poisson Random Timer +# +#timer.factor=1.0f + +# Default implementation that create the Timer structure to add to Test Plan +# Implementation of interface org.apache.jmeter.gui.action.thinktime.ThinkTimeCreator +#think_time_creator.impl=org.apache.jmeter.thinktime.DefaultThinkTimeCreator + +# Default Timer GUI class added to Test Plan by DefaultThinkTimeCreator +#think_time_creator.default_timer_implementation=org.apache.jmeter.timers.gui.UniformRandomTimerGui + +# Default constant pause of Timer +#think_time_creator.default_constant_pause=1000 + +# Default range pause of Timer +#think_time_creator.default_range=100 + + +# Change this parameter if you want to override the APDEX satisfaction threshold. +jmeter.reportgenerator.apdex_satisfied_threshold=500 + +# Change this parameter if you want to override the APDEX tolerance threshold. +jmeter.reportgenerator.apdex_tolerated_threshold=1500 + +#--------------------------------------------------------------------------- +# Naming Policy configuration +#--------------------------------------------------------------------------- + +# Prefix used when naming elements +#naming_policy.prefix= +# Suffix used when naming elements +#naming_policy.suffix= + +# Implementation of interface org.apache.jmeter.gui.action.TreeNodeNamingPolicy +#naming_policy.impl=org.apache.jmeter.gui.action.impl.DefaultTreeNodeNamingPolicy + From 6b0291d420d1b5695ce073d1a05c1602cd56454e Mon Sep 17 00:00:00 2001 From: "rick.parker" Date: Thu, 2 Nov 2017 11:44:27 +0000 Subject: [PATCH 02/37] Remote JMeter working from single JAR. --- tools/jmeter/build.gradle | 31 ++++++++++++------- .../net/corda/jmeter/TraderDemoPlugins.kt | 15 ++++----- .../src/main/resources/jmeter.properties | 2 +- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/tools/jmeter/build.gradle b/tools/jmeter/build.gradle index 150ec63690..65b170d3aa 100644 --- a/tools/jmeter/build.gradle +++ b/tools/jmeter/build.gradle @@ -1,7 +1,5 @@ apply plugin: 'kotlin' apply plugin: 'application' -//apply plugin: 'net.corda.plugins.cordapp' -//apply plugin: 'net.corda.plugins.cordformation' mainClassName = 'net.corda.jmeter.Launcher' @@ -29,17 +27,21 @@ dependencies { runtime group: 'org.apache.jmeter', name: 'ApacheJMeter', version: "$jmVersion" runtime group: 'org.apache.jmeter', name: 'jorphan', version: "$jmVersion" - compile project(":test-utils") + //compile project(":test-utils") compile project(":finance") - - //cordapp project(':finance') - //cordapp project(':samples:bank-of-corda-demo') - } -//jmeter { -// testFileDir = file("src/main/resources/jmeter") -//} +task(runServer, dependsOn: 'classes', type: JavaExec) { + classpath = sourceSets.main.runtimeClasspath + main = 'net.corda.jmeter.Launcher' + systemProperty "search_paths", project(':tools:jmeter').configurations.runtime.files.join(";") + systemProperty "java.rmi.server.hostname", InetAddress.getLocalHost().getHostName() + //jvmArgs += "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005" + args+= [ "-p", sourceSets.main.resources.getSrcDirs().first().getPath()+"/jmeter.properties", + "-d", sourceSets.main.resources.getSrcDirs().first().getPath(), + "-j", buildDir.getPath()+"/jmeter.log", + "-s" ] +} run { systemProperty "search_paths", project(':tools:jmeter').configurations.runtime.files.join(";") @@ -50,9 +52,16 @@ run { } jar { + from(configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) }) { + exclude "META-INF/*.SF" + exclude "META-INF/*.DSA" + exclude "META-INF/*.RSA" + } manifest { attributes( - 'Automatic-Module-Name': 'net.corda.tools.jmeter' + 'Automatic-Module-Name': 'net.corda.tools.jmeter', + 'Main-Class': mainClassName ) } + zip64 = true } \ No newline at end of file diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt b/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt index 1e91443840..4bfd9a6326 100644 --- a/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt +++ b/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt @@ -1,18 +1,13 @@ package net.corda.jmeter +import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.messaging.CordaRPCOps -import net.corda.core.messaging.startFlow import net.corda.core.utilities.OpaqueBytes -import net.corda.core.utilities.getOrThrow import net.corda.finance.DOLLARS import net.corda.finance.flows.CashIssueFlow import net.corda.jmeter.CordaRPCSampler.FlowInvoke -import net.corda.testing.DUMMY_BANK_A -import net.corda.testing.DUMMY_BANK_B -import net.corda.testing.contracts.calculateRandomlySizedAmounts import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext -import java.util.* abstract class AsbtractTraderDemoPlugin : CordaRPCSampler.Plugin { @@ -21,12 +16,14 @@ abstract class AsbtractTraderDemoPlugin : CordaRPCSampler.Plugin { lateinit var seller: Party lateinit var notary: Party + val bankA = CordaX500Name(organisation = "Bank A", locality = "London", country = "GB") + val bankB = CordaX500Name(organisation = "Bank B", locality = "New York", country = "US") + protected fun getIdentities(rpc: CordaRPCOps) { - buyer = rpc.wellKnownPartyFromX500Name(DUMMY_BANK_A.name) ?: throw IllegalStateException("Don't know ${DUMMY_BANK_A.name}") - seller = rpc.wellKnownPartyFromX500Name(DUMMY_BANK_B.name) ?: throw IllegalStateException("Don't know ${DUMMY_BANK_B.name}") + buyer = rpc.wellKnownPartyFromX500Name(bankA) ?: throw IllegalStateException("Don't know $bankA") + seller = rpc.wellKnownPartyFromX500Name(bankB) ?: throw IllegalStateException("Don't know $bankB") notary = rpc.notaryIdentities().first() } - } class CashIssuerPlugin : AsbtractTraderDemoPlugin() { diff --git a/tools/jmeter/src/main/resources/jmeter.properties b/tools/jmeter/src/main/resources/jmeter.properties index 87a42d2d91..4a7f1b6f66 100644 --- a/tools/jmeter/src/main/resources/jmeter.properties +++ b/tools/jmeter/src/main/resources/jmeter.properties @@ -695,7 +695,7 @@ wmlParser.types=text/vnd.wap.wml # default queue size #asynch.batch.queue.size=100 # Same as Asynch but strips response data from SampleResult -#mode=StrippedAsynch +mode=StrippedAsynch # # DiskStore: as for Hold mode, but serialises the samples to disk, rather than saving in memory #mode=DiskStore From ed898778c8a89495809e4bd9cb2ea41a31f3da8f Mon Sep 17 00:00:00 2001 From: "rick.parker" Date: Thu, 2 Nov 2017 14:58:47 +0000 Subject: [PATCH 03/37] Some clean up. Remote slave via capsule is working. --- tools/jmeter/build.gradle | 50 +++++++++++++------ .../main/kotlin/net/corda/jmeter/Launcher.kt | 12 ++++- .../src/main/resources/jmeter.properties | 2 +- .../src/main/resources/lib/ext/readme.txt | 1 + .../src/main/resources/lib/junit/readme.txt | 1 + tools/jmeter/src/main/resources/log4j2.xml | 21 ++++++++ 6 files changed, 69 insertions(+), 18 deletions(-) create mode 100644 tools/jmeter/src/main/resources/lib/ext/readme.txt create mode 100644 tools/jmeter/src/main/resources/lib/junit/readme.txt create mode 100644 tools/jmeter/src/main/resources/log4j2.xml diff --git a/tools/jmeter/build.gradle b/tools/jmeter/build.gradle index 65b170d3aa..7e7abbd9f8 100644 --- a/tools/jmeter/build.gradle +++ b/tools/jmeter/build.gradle @@ -1,10 +1,12 @@ apply plugin: 'kotlin' +apply plugin: 'us.kirchmeier.capsule' apply plugin: 'application' mainClassName = 'net.corda.jmeter.Launcher' dependencies { compile project(':client:rpc') + compile project(":finance") // JMeter ext.jmVersion = "3.3" @@ -14,7 +16,7 @@ dependencies { runtime group: 'org.apache.jmeter', name: 'ApacheJMeter_ftp', version: "$jmVersion" runtime group: 'org.apache.jmeter', name: 'ApacheJMeter_functions', version: "$jmVersion" runtime group: 'org.apache.jmeter', name: 'ApacheJMeter_http', version: "$jmVersion" - compile group: 'org.apache.jmeter', name: 'ApacheJMeter_java', version: "$jmVersion" + compile group: 'org.apache.jmeter', name: 'ApacheJMeter_java', version: "$jmVersion" // 'compile' because we extend Java sampler. runtime group: 'org.apache.jmeter', name: 'ApacheJMeter_jdbc', version: "$jmVersion" runtime group: 'org.apache.jmeter', name: 'ApacheJMeter_jms', version: "$jmVersion" runtime group: 'org.apache.jmeter', name: 'ApacheJMeter_junit', version: "$jmVersion" @@ -26,37 +28,29 @@ dependencies { runtime group: 'org.apache.jmeter', name: 'ApacheJMeter_config', version: "$jmVersion" runtime group: 'org.apache.jmeter', name: 'ApacheJMeter', version: "$jmVersion" runtime group: 'org.apache.jmeter', name: 'jorphan', version: "$jmVersion" - - //compile project(":test-utils") - compile project(":finance") } task(runServer, dependsOn: 'classes', type: JavaExec) { classpath = sourceSets.main.runtimeClasspath main = 'net.corda.jmeter.Launcher' systemProperty "search_paths", project(':tools:jmeter').configurations.runtime.files.join(";") - systemProperty "java.rmi.server.hostname", InetAddress.getLocalHost().getHostName() - //jvmArgs += "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005" + systemProperty "java.rmi.server.hostname", "0.0.0.0" + systemProperty "jmeter.home", sourceSets.main.resources.getSrcDirs().first().getPath() + // If you want to debug: jvmArgs += "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005" args+= [ "-p", sourceSets.main.resources.getSrcDirs().first().getPath()+"/jmeter.properties", "-d", sourceSets.main.resources.getSrcDirs().first().getPath(), - "-j", buildDir.getPath()+"/jmeter.log", "-s" ] } run { systemProperty "search_paths", project(':tools:jmeter').configurations.runtime.files.join(";") - //jvmArgs += "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005" + systemProperty "jmeter.home", sourceSets.main.resources.getSrcDirs().first().getPath() + // If you want to debug: jvmArgs += "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005" args+= [ "-p", sourceSets.main.resources.getSrcDirs().first().getPath()+"/jmeter.properties", - "-d", sourceSets.main.resources.getSrcDirs().first().getPath(), - "-j", buildDir.getPath()+"/jmeter.log" ] + "-d", sourceSets.main.resources.getSrcDirs().first().getPath() ] } jar { - from(configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) }) { - exclude "META-INF/*.SF" - exclude "META-INF/*.DSA" - exclude "META-INF/*.RSA" - } manifest { attributes( 'Automatic-Module-Name': 'net.corda.tools.jmeter', @@ -64,4 +58,28 @@ jar { ) } zip64 = true -} \ No newline at end of file +} + +// For building a runnable jar with no other dependencies for remote JMeter slave server, that has Corda code on classpath. +// Run with: java -jar corda-jmeter-.jar +// No additional args required but will be passed if specified. +task buildJMeterJAR(type: FatCapsule, dependsOn: 'jar') { + applicationClass 'net.corda.jmeter.Launcher' + archiveName "corda-jmeter-${corda_release_version}.jar" + applicationSource = files( + project(':tools:jmeter').jar + ) + from 'NOTICE' // Copy CDDL notice + from { "$rootDir/tools/jmeter/build/resources/main/jmeter.properties" } + from { "$rootDir/tools/jmeter/build/resources/main/log4j2.xml" } + + capsuleManifest { + applicationVersion = corda_release_version + systemProperties['java.rmi.server.hostname'] = '0.0.0.0' + minJavaVersion = '1.8.0' + minUpdateVersion['1.8'] = java8_minUpdateVersion + + // JVM configuration. Can be overridden on java command line. + jvmArgs = ['-Xms512m', '-Xmx512m', '-XX:+UseG1GC'] + } +} diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt index 47787db764..f06ce17a86 100644 --- a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt +++ b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt @@ -1,13 +1,23 @@ package net.corda.jmeter import org.apache.jmeter.JMeter +import org.slf4j.LoggerFactory class Launcher { companion object { @JvmStatic fun main(args: Array) { + val logger = LoggerFactory.getLogger(this::class.java) val jmeter = JMeter() - jmeter.start(args) + val capsuleDir = System.getProperty("capsule.dir") + if (capsuleDir != null) { + // We are running under Capsule, so assume we want a JMeter distributed server to be controlled from + // elsewhere. + logger.info("Starting JMeter in server mode from $capsuleDir") + jmeter.start(arrayOf("-s", "-d", capsuleDir, "-p", "$capsuleDir/jmeter.properties") + args) + } else { + jmeter.start(args) + } } } } \ No newline at end of file diff --git a/tools/jmeter/src/main/resources/jmeter.properties b/tools/jmeter/src/main/resources/jmeter.properties index 4a7f1b6f66..5fc50531b6 100644 --- a/tools/jmeter/src/main/resources/jmeter.properties +++ b/tools/jmeter/src/main/resources/jmeter.properties @@ -238,7 +238,7 @@ gui.quick_9=ViewResultsFullVisualizer #--------------------------------------------------------------------------- # Remote Hosts - comma delimited -remote_hosts=127.0.0.1 +remote_hosts=Rick-Parker.local #remote_hosts=localhost:1099,localhost:2010 # RMI port to be used by the server (must start rmiregistry with same port) diff --git a/tools/jmeter/src/main/resources/lib/ext/readme.txt b/tools/jmeter/src/main/resources/lib/ext/readme.txt new file mode 100644 index 0000000000..6243f40248 --- /dev/null +++ b/tools/jmeter/src/main/resources/lib/ext/readme.txt @@ -0,0 +1 @@ +This directory is expected by JMeter \ No newline at end of file diff --git a/tools/jmeter/src/main/resources/lib/junit/readme.txt b/tools/jmeter/src/main/resources/lib/junit/readme.txt new file mode 100644 index 0000000000..6243f40248 --- /dev/null +++ b/tools/jmeter/src/main/resources/lib/junit/readme.txt @@ -0,0 +1 @@ +This directory is expected by JMeter \ No newline at end of file diff --git a/tools/jmeter/src/main/resources/log4j2.xml b/tools/jmeter/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..7c81df7e7c --- /dev/null +++ b/tools/jmeter/src/main/resources/log4j2.xml @@ -0,0 +1,21 @@ + + + + info + + + + + + + + + + + + + + + + From b58744b18a7f89eb83d0b818bda4907e239c6c04 Mon Sep 17 00:00:00 2001 From: "rick.parker" Date: Thu, 2 Nov 2017 15:54:40 +0000 Subject: [PATCH 04/37] Full config of capsule launched JMeter server (was missing functions previously). --- .../main/kotlin/net/corda/jmeter/Launcher.kt | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt index f06ce17a86..c26dae07a1 100644 --- a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt +++ b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt @@ -1,7 +1,11 @@ package net.corda.jmeter +import net.corda.core.internal.div import org.apache.jmeter.JMeter import org.slf4j.LoggerFactory +import java.nio.file.Files +import java.nio.file.Paths +import kotlin.streams.asSequence class Launcher { companion object { @@ -11,10 +15,22 @@ class Launcher { val jmeter = JMeter() val capsuleDir = System.getProperty("capsule.dir") if (capsuleDir != null) { - // We are running under Capsule, so assume we want a JMeter distributed server to be controlled from + // We are running under Capsule, so assume we want a JMeter slave server to be controlled from // elsewhere. logger.info("Starting JMeter in server mode from $capsuleDir") - jmeter.start(arrayOf("-s", "-d", capsuleDir, "-p", "$capsuleDir/jmeter.properties") + args) + // Add all JMeter and Corda jars onto the JMeter search_paths + val searchPath = Files.list(Paths.get(capsuleDir)).asSequence().filter { + val filename = it.fileName.toString() + filename.endsWith(".jar") && (filename.contains("corda") || filename.contains("jmeter", true)) + }.joinToString(";") + logger.info("search_paths = $searchPath") + System.setProperty("search_paths", searchPath) + // Set the JMeter home as a property rather than command line arg, due to inconsistent code in JMeter. + System.setProperty("jmeter.home", capsuleDir) + // Create two dirs that JMeter expects, if they don't already exist. + Files.createDirectories(Paths.get(capsuleDir) / "lib" / "ext") + Files.createDirectories(Paths.get(capsuleDir) / "lib" / "junit") + jmeter.start(arrayOf("-s", "-p", "$capsuleDir/jmeter.properties") + args) } else { jmeter.start(args) } From 501f6c4a1ae0903203bf9f03216a2ba28f734af2 Mon Sep 17 00:00:00 2001 From: "rick.parker" Date: Fri, 10 Nov 2017 08:33:55 +0000 Subject: [PATCH 05/37] SSH tunnelling utility. Property files per remote host. --- tools/jmeter/build.gradle | 21 +++- .../main/kotlin/net/corda/jmeter/Launcher.kt | 20 +++- .../src/main/kotlin/net/corda/jmeter/Ssh.kt | 102 ++++++++++++++++++ .../src/main/resources/Java Request.jmx | 4 +- .../src/main/resources/jmeter.properties | 6 +- .../src/main/resources/perf-node-1.properties | 1 + .../src/main/resources/perf-node-2.properties | 1 + .../src/main/resources/perf-node-3.properties | 1 + .../src/main/resources/perf-node-4.properties | 1 + .../src/main/resources/perf-notary.properties | 1 + 10 files changed, 145 insertions(+), 13 deletions(-) create mode 100644 tools/jmeter/src/main/kotlin/net/corda/jmeter/Ssh.kt create mode 100644 tools/jmeter/src/main/resources/perf-node-1.properties create mode 100644 tools/jmeter/src/main/resources/perf-node-2.properties create mode 100644 tools/jmeter/src/main/resources/perf-node-3.properties create mode 100644 tools/jmeter/src/main/resources/perf-node-4.properties create mode 100644 tools/jmeter/src/main/resources/perf-notary.properties diff --git a/tools/jmeter/build.gradle b/tools/jmeter/build.gradle index 7e7abbd9f8..e74d78026c 100644 --- a/tools/jmeter/build.gradle +++ b/tools/jmeter/build.gradle @@ -6,7 +6,8 @@ mainClassName = 'net.corda.jmeter.Launcher' dependencies { compile project(':client:rpc') - compile project(":finance") + compile project(':finance') + compile project(':tools:loadtest') // JMeter ext.jmVersion = "3.3" @@ -42,8 +43,18 @@ task(runServer, dependsOn: 'classes', type: JavaExec) { "-s" ] } +task(runSsh, dependsOn: 'classes', type: JavaExec) { + classpath = sourceSets.main.runtimeClasspath + main = 'net.corda.jmeter.Ssh' + if ( project.hasProperty("jmeterHosts") ) { + args Eval.me(jmeterHosts) + } + standardInput = System.in +} + run { systemProperty "search_paths", project(':tools:jmeter').configurations.runtime.files.join(";") + systemProperty "java.rmi.server.hostname", "localhost" systemProperty "jmeter.home", sourceSets.main.resources.getSrcDirs().first().getPath() // If you want to debug: jvmArgs += "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005" args+= [ "-p", sourceSets.main.resources.getSrcDirs().first().getPath()+"/jmeter.properties", @@ -70,12 +81,14 @@ task buildJMeterJAR(type: FatCapsule, dependsOn: 'jar') { project(':tools:jmeter').jar ) from 'NOTICE' // Copy CDDL notice - from { "$rootDir/tools/jmeter/build/resources/main/jmeter.properties" } - from { "$rootDir/tools/jmeter/build/resources/main/log4j2.xml" } + from("$rootDir/tools/jmeter/build/resources/main") { + include "log4j2.xml" + include "*.properties" + } capsuleManifest { applicationVersion = corda_release_version - systemProperties['java.rmi.server.hostname'] = '0.0.0.0' + systemProperties['java.rmi.server.hostname'] = 'localhost' minJavaVersion = '1.8.0' minUpdateVersion['1.8'] = java8_minUpdateVersion diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt index c26dae07a1..a108da4b89 100644 --- a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt +++ b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt @@ -3,6 +3,7 @@ package net.corda.jmeter import net.corda.core.internal.div import org.apache.jmeter.JMeter import org.slf4j.LoggerFactory +import java.net.InetAddress import java.nio.file.Files import java.nio.file.Paths import kotlin.streams.asSequence @@ -18,8 +19,9 @@ class Launcher { // We are running under Capsule, so assume we want a JMeter slave server to be controlled from // elsewhere. logger.info("Starting JMeter in server mode from $capsuleDir") + val capsuleDirPath = Paths.get(capsuleDir) // Add all JMeter and Corda jars onto the JMeter search_paths - val searchPath = Files.list(Paths.get(capsuleDir)).asSequence().filter { + val searchPath = Files.list(capsuleDirPath).asSequence().filter { val filename = it.fileName.toString() filename.endsWith(".jar") && (filename.contains("corda") || filename.contains("jmeter", true)) }.joinToString(";") @@ -28,9 +30,19 @@ class Launcher { // Set the JMeter home as a property rather than command line arg, due to inconsistent code in JMeter. System.setProperty("jmeter.home", capsuleDir) // Create two dirs that JMeter expects, if they don't already exist. - Files.createDirectories(Paths.get(capsuleDir) / "lib" / "ext") - Files.createDirectories(Paths.get(capsuleDir) / "lib" / "junit") - jmeter.start(arrayOf("-s", "-p", "$capsuleDir/jmeter.properties") + args) + Files.createDirectories(capsuleDirPath / "lib" / "ext") + Files.createDirectories(capsuleDirPath / "lib" / "junit") + // Now see if we have a hostname specific property file, and if so, add it. + val hostName = InetAddress.getLocalHost().hostName + val hostSpecificConfigFile = capsuleDirPath / "$hostName.properties" + logger.info("Attempting to use host-specific properties file $hostSpecificConfigFile") + val extraArgs = if (Files.exists(hostSpecificConfigFile)) { + logger.info("Found host-specific properties file") + arrayOf("-q", hostSpecificConfigFile.toString()) + } else { + emptyArray() + } + jmeter.start(arrayOf("-s", "-p", (capsuleDirPath / "jmeter.properties").toString()) + extraArgs + args) } else { jmeter.start(args) } diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Ssh.kt b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Ssh.kt new file mode 100644 index 0000000000..74a0fa1036 --- /dev/null +++ b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Ssh.kt @@ -0,0 +1,102 @@ +package net.corda.jmeter + +import com.jcraft.jsch.JSch +import com.jcraft.jsch.Session +import net.corda.core.utilities.NetworkHostAndPort +import net.corda.loadtest.setupJSchWithSshAgent +import org.slf4j.LoggerFactory +import java.io.BufferedReader +import java.io.InputStreamReader +import java.util.* + + +class Ssh { + companion object { + val log = LoggerFactory.getLogger(this::class.java) + + @JvmStatic + fun main(args: Array) { + val userName = System.getProperty("user.name") + val jsch = setupJSchWithSshAgent() + val sessions = mutableListOf() + + // Read jmeter.properties + // For each host:port combo, map them to hosts from command line + + val jmeterProps = loadProps("/jmeter.properties") + // The port the JMeter remote agents call back to on this client host. + val clientRmiLocalPort = jmeterProps.getProperty("client.rmi.localport").toInt() + // TODO: Where is this value used? Just on the remote agent to set up the RMI registry? + val serverRmiPort = jmeterProps.getProperty("server.rmi.port", "1099").toInt() + + // Where JMeter driver will try to connect for remote agents (should all be localhost so can ssh tunnel). + val localHostsAndPorts = jmeterProps.getProperty("remote_hosts", "").split(',').map { it.trim() } + args.zip(localHostsAndPorts) { remoteHost, localHostAndPortString -> + // Actual remote host and port we will tunnel to. + log.info("Creating tunnels for $remoteHost") + val localHostAndPort = NetworkHostAndPort.parse(localHostAndPortString) + + // For the remote host, load their specific property file, since it specifies remote RMI server port + val unqualifiedHostName = remoteHost.substringBefore('.') + val hostProps = loadProps("/$unqualifiedHostName.properties") + + val serverRmiLocalPort = hostProps.getProperty("server.rmi.localport", jmeterProps.getProperty("server.rmi.localport")).toInt() + + val session = connectToHost(jsch, remoteHost, userName) + sessions += session + + // TODO: maybe check the local host is actually "localhost"? + // For tunnelling the RMI registry on the remote agent + // ssh ${remoteHostAndPort.host} -L 0.0.0.0:${localHostAndPort.port}:localhost:$serverRmiPort -N + createOutboundTunnel(session, NetworkHostAndPort("0.0.0.0", localHostAndPort.port), NetworkHostAndPort("localhost", serverRmiPort)) + + // For tunnelling the actual connection to the remote agent + // ssh ${remoteHostAndPort.host} -L 0.0.0.0:$serverRmiLocalPort:localhost:$serverRmiLocalPort -N + createOutboundTunnel(session, NetworkHostAndPort("0.0.0.0", serverRmiLocalPort), NetworkHostAndPort("localhost", serverRmiLocalPort)) + + // For returning results to the client + // ssh ${remoteHostAndPort.host} -R 0.0.0.0:clientRmiLocalPort:localhost:clientRmiLocalPort -N + createInboundTunnel(session, NetworkHostAndPort("0.0.0.0", clientRmiLocalPort), NetworkHostAndPort("localhost", clientRmiLocalPort)) + } + val input = BufferedReader(InputStreamReader(System.`in`)) + + do { + log.info("Type 'quit' to exit cleanly.") + } while (input.readLine() != "quit") + sessions.forEach { + log.info("Closing tunnels for ${it.host}") + it.disconnect() + } + } + + private fun loadProps(filename: String): Properties { + val props = Properties() + this::class.java.getResourceAsStream(filename).use { + props.load(it) + } + return props + } + + fun connectToHost(jSch: JSch, remoteHost: String, remoteUserName: String): Session { + val session = jSch.getSession(remoteUserName, remoteHost, 22) + // We don't check the host fingerprints because they may change often + session.setConfig("StrictHostKeyChecking", "no") + log.info("Connecting to $remoteHost...") + session.connect() + log.info("Connected to $remoteHost!") + return session + } + + fun createOutboundTunnel(session: Session, local: NetworkHostAndPort, remote: NetworkHostAndPort) { + log.info("Creating outbound tunnel from $local to $remote with ${session.host}...") + session.setPortForwardingL(local.host, local.port, remote.host, remote.port) + log.info("Tunnel created!") + } + + fun createInboundTunnel(session: Session, local: NetworkHostAndPort, remote: NetworkHostAndPort) { + log.info("Creating inbound tunnel from $remote to $local on ${session.host}...") + session.setPortForwardingR(remote.host, remote.port, local.host, local.port) + log.info("Tunnel created!") + } + } +} \ No newline at end of file diff --git a/tools/jmeter/src/main/resources/Java Request.jmx b/tools/jmeter/src/main/resources/Java Request.jmx index 974d09bfac..e0718d0211 100644 --- a/tools/jmeter/src/main/resources/Java Request.jmx +++ b/tools/jmeter/src/main/resources/Java Request.jmx @@ -15,9 +15,9 @@ continue false - 1000 + 10 - 2 + 3 1509455820000 1509455820000 diff --git a/tools/jmeter/src/main/resources/jmeter.properties b/tools/jmeter/src/main/resources/jmeter.properties index 5fc50531b6..c4d001f71d 100644 --- a/tools/jmeter/src/main/resources/jmeter.properties +++ b/tools/jmeter/src/main/resources/jmeter.properties @@ -238,7 +238,7 @@ gui.quick_9=ViewResultsFullVisualizer #--------------------------------------------------------------------------- # Remote Hosts - comma delimited -remote_hosts=Rick-Parker.local +remote_hosts=127.0.0.1:20099 #remote_hosts=localhost:1099,localhost:2010 # RMI port to be used by the server (must start rmiregistry with same port) @@ -261,7 +261,7 @@ remote_hosts=Rick-Parker.local # Parameter that controls the RMI port used by the RemoteSampleListenerImpl (The Controler) # Default value is 0 which means port is randomly assigned # You may need to open Firewall port on the Controller machine -#client.rmi.localport=0 +client.rmi.localport=4001 # When distributed test is starting, there may be several attempts to initialize # remote engines. By default, only single try is made. Increase following property @@ -280,7 +280,7 @@ remote_hosts=Rick-Parker.local # To use a specific port for the JMeter server engine, define # the following property before starting the server: -#server.rmi.localport=4000 +server.rmi.localport=5000 # From JMeter 2.3.1, the jmeter server creates the RMI registry as part of the server process. # To stop the server creating the RMI registry: diff --git a/tools/jmeter/src/main/resources/perf-node-1.properties b/tools/jmeter/src/main/resources/perf-node-1.properties new file mode 100644 index 0000000000..3b29059afc --- /dev/null +++ b/tools/jmeter/src/main/resources/perf-node-1.properties @@ -0,0 +1 @@ +server.rmi.localport=10101 diff --git a/tools/jmeter/src/main/resources/perf-node-2.properties b/tools/jmeter/src/main/resources/perf-node-2.properties new file mode 100644 index 0000000000..97a2fed339 --- /dev/null +++ b/tools/jmeter/src/main/resources/perf-node-2.properties @@ -0,0 +1 @@ +server.rmi.localport=10102 diff --git a/tools/jmeter/src/main/resources/perf-node-3.properties b/tools/jmeter/src/main/resources/perf-node-3.properties new file mode 100644 index 0000000000..4381c5a309 --- /dev/null +++ b/tools/jmeter/src/main/resources/perf-node-3.properties @@ -0,0 +1 @@ +server.rmi.localport=10103 diff --git a/tools/jmeter/src/main/resources/perf-node-4.properties b/tools/jmeter/src/main/resources/perf-node-4.properties new file mode 100644 index 0000000000..c1a29df71d --- /dev/null +++ b/tools/jmeter/src/main/resources/perf-node-4.properties @@ -0,0 +1 @@ +server.rmi.localport=10104 diff --git a/tools/jmeter/src/main/resources/perf-notary.properties b/tools/jmeter/src/main/resources/perf-notary.properties new file mode 100644 index 0000000000..b307059e23 --- /dev/null +++ b/tools/jmeter/src/main/resources/perf-notary.properties @@ -0,0 +1 @@ +server.rmi.localport=10100 From 2650be356bdbddfcc60cf61e0627e100ed855dd9 Mon Sep 17 00:00:00 2001 From: "rick.parker" Date: Fri, 10 Nov 2017 09:56:25 +0000 Subject: [PATCH 06/37] Rename jar to make easier to deploy with wildcard filters. --- tools/jmeter/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/jmeter/build.gradle b/tools/jmeter/build.gradle index e74d78026c..4d8d53bc58 100644 --- a/tools/jmeter/build.gradle +++ b/tools/jmeter/build.gradle @@ -76,7 +76,7 @@ jar { // No additional args required but will be passed if specified. task buildJMeterJAR(type: FatCapsule, dependsOn: 'jar') { applicationClass 'net.corda.jmeter.Launcher' - archiveName "corda-jmeter-${corda_release_version}.jar" + archiveName "jmeter-corda-${corda_release_version}.jar" applicationSource = files( project(':tools:jmeter').jar ) From 1570ea3b53b9eb2dbe114709a40caa61ef082415 Mon Sep 17 00:00:00 2001 From: "rick.parker" Date: Fri, 10 Nov 2017 11:40:12 +0000 Subject: [PATCH 07/37] Easy all in one launch of UI + SSH tunnels. --- tools/jmeter/build.gradle | 4 +++ .../main/kotlin/net/corda/jmeter/Launcher.kt | 16 ++++++++++- .../src/main/kotlin/net/corda/jmeter/Ssh.kt | 27 +++++++++++++------ 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/tools/jmeter/build.gradle b/tools/jmeter/build.gradle index 4d8d53bc58..216fb9a25f 100644 --- a/tools/jmeter/build.gradle +++ b/tools/jmeter/build.gradle @@ -59,6 +59,10 @@ run { // If you want to debug: jvmArgs += "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005" args+= [ "-p", sourceSets.main.resources.getSrcDirs().first().getPath()+"/jmeter.properties", "-d", sourceSets.main.resources.getSrcDirs().first().getPath() ] + if ( project.hasProperty("jmeterHosts") ) { + args+= "-Xssh" + args+= Eval.me(jmeterHosts) + } } jar { diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt index a108da4b89..827e76cef4 100644 --- a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt +++ b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt @@ -13,6 +13,7 @@ class Launcher { @JvmStatic fun main(args: Array) { val logger = LoggerFactory.getLogger(this::class.java) + logger.info("Launcher called with ${args.toList()}") val jmeter = JMeter() val capsuleDir = System.getProperty("capsule.dir") if (capsuleDir != null) { @@ -44,8 +45,21 @@ class Launcher { } jmeter.start(arrayOf("-s", "-p", (capsuleDirPath / "jmeter.properties").toString()) + extraArgs + args) } else { - jmeter.start(args) + jmeter.start(maybeOpenSshTunnels(args)) } } + + private fun maybeOpenSshTunnels(args: Array): Array { + var index = 0 + for (arg in args) { + if (arg == "-Xssh") { + // start ssh + Ssh.main(args.copyOfRange(index + 1, args.size), false) + return if (index == 0) emptyArray() else args.copyOfRange(0, index) + } + index++ + } + return args + } } } \ No newline at end of file diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Ssh.kt b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Ssh.kt index 74a0fa1036..47060966ca 100644 --- a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Ssh.kt +++ b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Ssh.kt @@ -4,6 +4,7 @@ import com.jcraft.jsch.JSch import com.jcraft.jsch.Session import net.corda.core.utilities.NetworkHostAndPort import net.corda.loadtest.setupJSchWithSshAgent +import net.corda.nodeapi.internal.addShutdownHook import org.slf4j.LoggerFactory import java.io.BufferedReader import java.io.InputStreamReader @@ -15,7 +16,8 @@ class Ssh { val log = LoggerFactory.getLogger(this::class.java) @JvmStatic - fun main(args: Array) { + @JvmOverloads + fun main(args: Array, wait: Boolean = true) { val userName = System.getProperty("user.name") val jsch = setupJSchWithSshAgent() val sessions = mutableListOf() @@ -58,14 +60,23 @@ class Ssh { // ssh ${remoteHostAndPort.host} -R 0.0.0.0:clientRmiLocalPort:localhost:clientRmiLocalPort -N createInboundTunnel(session, NetworkHostAndPort("0.0.0.0", clientRmiLocalPort), NetworkHostAndPort("localhost", clientRmiLocalPort)) } - val input = BufferedReader(InputStreamReader(System.`in`)) - do { - log.info("Type 'quit' to exit cleanly.") - } while (input.readLine() != "quit") - sessions.forEach { - log.info("Closing tunnels for ${it.host}") - it.disconnect() + if (wait) { + val input = BufferedReader(InputStreamReader(System.`in`)) + do { + log.info("Type 'quit' to exit cleanly.") + } while (input.readLine() != "quit") + sessions.forEach { + log.info("Closing tunnels for ${it.host}") + it.disconnect() + } + } else { + addShutdownHook { + sessions.forEach { + log.info("Closing tunnels for ${it.host}") + it.disconnect() + } + } } } From 9be3098b601e0b201f19fa06f1d06ed7d5b4b4dc Mon Sep 17 00:00:00 2001 From: "rick.parker" Date: Fri, 10 Nov 2017 16:30:07 +0000 Subject: [PATCH 08/37] Comment out parties. --- .../src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt b/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt index 4bfd9a6326..a126140b31 100644 --- a/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt +++ b/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt @@ -20,8 +20,8 @@ abstract class AsbtractTraderDemoPlugin : CordaRPCSampler.Plugin { val bankB = CordaX500Name(organisation = "Bank B", locality = "New York", country = "US") protected fun getIdentities(rpc: CordaRPCOps) { - buyer = rpc.wellKnownPartyFromX500Name(bankA) ?: throw IllegalStateException("Don't know $bankA") - seller = rpc.wellKnownPartyFromX500Name(bankB) ?: throw IllegalStateException("Don't know $bankB") + //buyer = rpc.wellKnownPartyFromX500Name(bankA) ?: throw IllegalStateException("Don't know $bankA") + //seller = rpc.wellKnownPartyFromX500Name(bankB) ?: throw IllegalStateException("Don't know $bankB") notary = rpc.notaryIdentities().first() } } From c5431294080e6eb4c360ae5290d1be57bc81c2f0 Mon Sep 17 00:00:00 2001 From: "rick.parker" Date: Mon, 13 Nov 2017 11:33:13 +0000 Subject: [PATCH 09/37] Work around for notary. --- .../kotlin/net/corda/jmeter/TraderDemoPlugins.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt b/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt index a126140b31..357a1067f0 100644 --- a/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt +++ b/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt @@ -12,17 +12,19 @@ import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext abstract class AsbtractTraderDemoPlugin : CordaRPCSampler.Plugin { - lateinit var buyer: Party - lateinit var seller: Party + //lateinit var buyer: Party + //lateinit var seller: Party lateinit var notary: Party - val bankA = CordaX500Name(organisation = "Bank A", locality = "London", country = "GB") - val bankB = CordaX500Name(organisation = "Bank B", locality = "New York", country = "US") + //val bankA = CordaX500Name(organisation = "Bank A", locality = "London", country = "GB") + //val bankB = CordaX500Name(organisation = "Bank B", locality = "New York", country = "US") + val node1 = CordaX500Name(commonName = null, state = null, organisation = "Perf-10.155.0.4", organisationUnit = "Corda", locality = "London", country = "GB") protected fun getIdentities(rpc: CordaRPCOps) { //buyer = rpc.wellKnownPartyFromX500Name(bankA) ?: throw IllegalStateException("Don't know $bankA") //seller = rpc.wellKnownPartyFromX500Name(bankB) ?: throw IllegalStateException("Don't know $bankB") - notary = rpc.notaryIdentities().first() + //notary = rpc.notaryIdentities().first() + notary = rpc.wellKnownPartyFromX500Name(node1) ?: throw IllegalStateException("Don't know $node1") } } From b0a8c3c3052ed9320cdb7a607ab5e4e84fbae06d Mon Sep 17 00:00:00 2001 From: "rick.parker" Date: Tue, 14 Nov 2017 13:48:35 +0000 Subject: [PATCH 10/37] Clean up, renaming etc --- tools/jmeter/build.gradle | 8 +- .../r3/corda/jmeter/FlowSampler.kt} | 36 ++++---- .../{net => com/r3}/corda/jmeter/Launcher.kt | 2 +- .../kotlin/com/r3/corda/jmeter/Samplers.kt | 44 +++++++++ .../{net => com/r3}/corda/jmeter/Ssh.kt | 2 +- .../net/corda/jmeter/TraderDemoPlugins.kt | 46 ---------- ...a Request.jmx => Example Flow Request.jmx} | 90 +++++++++---------- .../src/main/resources/jmeter.properties | 2 +- 8 files changed, 115 insertions(+), 115 deletions(-) rename tools/jmeter/src/main/kotlin/{net/corda/jmeter/CordaRPCSampler.kt => com/r3/corda/jmeter/FlowSampler.kt} (71%) rename tools/jmeter/src/main/kotlin/{net => com/r3}/corda/jmeter/Launcher.kt (99%) create mode 100644 tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt rename tools/jmeter/src/main/kotlin/{net => com/r3}/corda/jmeter/Ssh.kt (99%) delete mode 100644 tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt rename tools/jmeter/src/main/resources/{Java Request.jmx => Example Flow Request.jmx} (92%) diff --git a/tools/jmeter/build.gradle b/tools/jmeter/build.gradle index 216fb9a25f..0f43927b39 100644 --- a/tools/jmeter/build.gradle +++ b/tools/jmeter/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'kotlin' apply plugin: 'us.kirchmeier.capsule' apply plugin: 'application' -mainClassName = 'net.corda.jmeter.Launcher' +mainClassName = 'com.r3.corda.jmeter.Launcher' dependencies { compile project(':client:rpc') @@ -33,7 +33,7 @@ dependencies { task(runServer, dependsOn: 'classes', type: JavaExec) { classpath = sourceSets.main.runtimeClasspath - main = 'net.corda.jmeter.Launcher' + main = 'com.r3.corda.jmeter.Launcher' systemProperty "search_paths", project(':tools:jmeter').configurations.runtime.files.join(";") systemProperty "java.rmi.server.hostname", "0.0.0.0" systemProperty "jmeter.home", sourceSets.main.resources.getSrcDirs().first().getPath() @@ -45,7 +45,7 @@ task(runServer, dependsOn: 'classes', type: JavaExec) { task(runSsh, dependsOn: 'classes', type: JavaExec) { classpath = sourceSets.main.runtimeClasspath - main = 'net.corda.jmeter.Ssh' + main = 'com.r3.corda.jmeter.Ssh' if ( project.hasProperty("jmeterHosts") ) { args Eval.me(jmeterHosts) } @@ -79,7 +79,7 @@ jar { // Run with: java -jar corda-jmeter-.jar // No additional args required but will be passed if specified. task buildJMeterJAR(type: FatCapsule, dependsOn: 'jar') { - applicationClass 'net.corda.jmeter.Launcher' + applicationClass 'com.r3.corda.jmeter.Launcher' archiveName "jmeter-corda-${corda_release_version}.jar" applicationSource = files( project(':tools:jmeter').jar diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/CordaRPCSampler.kt b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/FlowSampler.kt similarity index 71% rename from tools/jmeter/src/main/kotlin/net/corda/jmeter/CordaRPCSampler.kt rename to tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/FlowSampler.kt index b6d385fec1..206937da97 100644 --- a/tools/jmeter/src/main/kotlin/net/corda/jmeter/CordaRPCSampler.kt +++ b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/FlowSampler.kt @@ -1,4 +1,4 @@ -package net.corda.jmeter +package com.r3.corda.jmeter import net.corda.client.rpc.CordaRPCClient import net.corda.client.rpc.CordaRPCConnection @@ -12,25 +12,30 @@ import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext import org.apache.jmeter.samplers.SampleResult -class CordaRPCSampler() : AbstractJavaSamplerClient() { +abstract class FlowSampler() : AbstractJavaSamplerClient() { companion object { val host = Argument("host", "localhost", "", "The remote network address (hostname or IP address) to connect to for RPC.") val port = Argument("port", "10000", "", "The remote port to connect to for RPC.") val username = Argument("username", "corda", "", "The RPC user to connect to connect as.") val password = Argument("password", "corda_is_awesome", "", "The password for the RPC user.") - val className = Argument("pluginClassName", "", "", "The class name of the implementation of ${CordaRPCSampler.Plugin::class.java}.") - val allArgs = setOf(host, port, username, password, className) + val allArgs = setOf(host, port, username, password) } var rpcClient: CordaRPCClient? = null var rpcConnection: CordaRPCConnection? = null var rpcProxy: CordaRPCOps? = null - var plugin: Plugin? = null override fun getDefaultParameters(): Arguments { // Add copies of all args, since they seem to be mutable. - return Arguments().apply { for(arg in allArgs) { addArgument(arg.clone() as Argument) } } + return Arguments().apply { + for (arg in allArgs) { + addArgument(arg.clone() as Argument) + } + for (arg in additionalArgs) { + addArgument(arg.clone() as Argument) + } + } } override fun setupTest(context: JavaSamplerContext) { @@ -38,12 +43,11 @@ class CordaRPCSampler() : AbstractJavaSamplerClient() { rpcClient = CordaRPCClient(NetworkHostAndPort(context.getParameter(host.name), context.getIntParameter(port.name))) rpcConnection = rpcClient!!.start(context.getParameter(username.name), context.getParameter(password.name)) rpcProxy = rpcConnection!!.proxy - plugin = Class.forName(context.getParameter(className.name)).newInstance() as Plugin - plugin!!.setupTest(rpcProxy!!, context) + setupTest(rpcProxy!!, context) } override fun runTest(context: JavaSamplerContext): SampleResult { - val flowInvoke = plugin!!.createFlowInvoke(rpcProxy!!, context) + val flowInvoke = createFlowInvoke(rpcProxy!!, context) val result = SampleResult() result.sampleStart() val handle = rpcProxy!!.startFlowDynamic(flowInvoke!!.flowLogicClass, *(flowInvoke!!.args)) @@ -55,7 +59,7 @@ class CordaRPCSampler() : AbstractJavaSamplerClient() { return result.apply { isSuccessful = true } - } catch(e: Exception) { + } catch (e: Exception) { result.sampleEnd() return result.apply { isSuccessful = false @@ -64,8 +68,7 @@ class CordaRPCSampler() : AbstractJavaSamplerClient() { } override fun teardownTest(context: JavaSamplerContext) { - plugin!!.teardownTest(rpcProxy!!, context) - plugin = null + teardownTest(rpcProxy!!, context) rpcProxy = null rpcConnection!!.close() rpcConnection = null @@ -73,11 +76,10 @@ class CordaRPCSampler() : AbstractJavaSamplerClient() { super.teardownTest(context) } - interface Plugin { - fun setupTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) - fun createFlowInvoke(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext): FlowInvoke<*> - fun teardownTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) - } + abstract val additionalArgs: Set + abstract fun setupTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) + abstract fun createFlowInvoke(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext): FlowInvoke<*> + abstract fun teardownTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) class FlowInvoke>(val flowLogicClass: Class, val args: Array) } \ No newline at end of file diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Launcher.kt similarity index 99% rename from tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt rename to tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Launcher.kt index 827e76cef4..b45302471f 100644 --- a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt +++ b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Launcher.kt @@ -1,4 +1,4 @@ -package net.corda.jmeter +package com.r3.corda.jmeter import net.corda.core.internal.div import org.apache.jmeter.JMeter diff --git a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt new file mode 100644 index 0000000000..e5fc610e2f --- /dev/null +++ b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt @@ -0,0 +1,44 @@ +package com.r3.corda.jmeter + +import net.corda.core.identity.CordaX500Name +import net.corda.core.identity.Party +import net.corda.core.messaging.CordaRPCOps +import net.corda.core.utilities.OpaqueBytes +import net.corda.finance.DOLLARS +import net.corda.finance.flows.CashIssueFlow +import org.apache.jmeter.config.Argument +import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext + + +abstract class AbstractSampler : FlowSampler() { + lateinit var notaryIdentity: Party + + companion object JMeterProperties { + val notary = Argument("notaryName", "", "", "The X500 name of the notary.") + } + + protected fun getIdentities(rpc: CordaRPCOps, testContext: JavaSamplerContext) { + if (!testContext.containsParameter(notary.name)) { + throw IllegalStateException("You must specify the '${notary.name}' property.") + } + val notaryName = CordaX500Name.parse(testContext.getParameter(notary.name)) + notaryIdentity = rpc.wellKnownPartyFromX500Name(notaryName) ?: throw IllegalStateException("Don't know $notaryName") + } +} + +class CashIssueSampler : AbstractSampler() { + override val additionalArgs: Set = setOf(notary) + + override fun setupTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) { + getIdentities(rpcProxy, testContext) + } + + override fun teardownTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) { + } + + override fun createFlowInvoke(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext): FlowInvoke { + val amount = 1_100_000_000_000.DOLLARS + return FlowInvoke(CashIssueFlow::class.java, arrayOf(amount, OpaqueBytes.of(1), notaryIdentity)) + } + +} \ No newline at end of file diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Ssh.kt b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Ssh.kt similarity index 99% rename from tools/jmeter/src/main/kotlin/net/corda/jmeter/Ssh.kt rename to tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Ssh.kt index 47060966ca..3c4a38391a 100644 --- a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Ssh.kt +++ b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Ssh.kt @@ -1,4 +1,4 @@ -package net.corda.jmeter +package com.r3.corda.jmeter import com.jcraft.jsch.JSch import com.jcraft.jsch.Session diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt b/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt deleted file mode 100644 index 357a1067f0..0000000000 --- a/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt +++ /dev/null @@ -1,46 +0,0 @@ -package net.corda.jmeter - -import net.corda.core.identity.CordaX500Name -import net.corda.core.identity.Party -import net.corda.core.messaging.CordaRPCOps -import net.corda.core.utilities.OpaqueBytes -import net.corda.finance.DOLLARS -import net.corda.finance.flows.CashIssueFlow -import net.corda.jmeter.CordaRPCSampler.FlowInvoke -import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext - - -abstract class AsbtractTraderDemoPlugin : CordaRPCSampler.Plugin { - - //lateinit var buyer: Party - //lateinit var seller: Party - lateinit var notary: Party - - //val bankA = CordaX500Name(organisation = "Bank A", locality = "London", country = "GB") - //val bankB = CordaX500Name(organisation = "Bank B", locality = "New York", country = "US") - val node1 = CordaX500Name(commonName = null, state = null, organisation = "Perf-10.155.0.4", organisationUnit = "Corda", locality = "London", country = "GB") - - protected fun getIdentities(rpc: CordaRPCOps) { - //buyer = rpc.wellKnownPartyFromX500Name(bankA) ?: throw IllegalStateException("Don't know $bankA") - //seller = rpc.wellKnownPartyFromX500Name(bankB) ?: throw IllegalStateException("Don't know $bankB") - //notary = rpc.notaryIdentities().first() - notary = rpc.wellKnownPartyFromX500Name(node1) ?: throw IllegalStateException("Don't know $node1") - } -} - -class CashIssuerPlugin : AsbtractTraderDemoPlugin() { - override fun setupTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) { - getIdentities(rpcProxy) - } - - override fun teardownTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) { - } - - override fun createFlowInvoke(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext): FlowInvoke { - val amount = 1_100_000_000_000.DOLLARS - //val amounts = calculateRandomlySizedAmounts(amount, 3, 10, Random()) - //rpc.startFlow(net.corda.finance.flows::CashIssueFlow, amount, OpaqueBytes.of(1), notary).returnValue.getOrThrow() - return FlowInvoke(CashIssueFlow::class.java, arrayOf(amount, OpaqueBytes.of(1), notary)) - } - -} \ No newline at end of file diff --git a/tools/jmeter/src/main/resources/Java Request.jmx b/tools/jmeter/src/main/resources/Example Flow Request.jmx similarity index 92% rename from tools/jmeter/src/main/resources/Java Request.jmx rename to tools/jmeter/src/main/resources/Example Flow Request.jmx index e0718d0211..257573d05e 100644 --- a/tools/jmeter/src/main/resources/Java Request.jmx +++ b/tools/jmeter/src/main/resources/Example Flow Request.jmx @@ -15,7 +15,7 @@ continue false - 10 + 1000 3 @@ -26,7 +26,7 @@ - + @@ -36,66 +36,30 @@ port - 10012 + 10003 = username - demo + corda = password - demo + corda_is_awesome = - - pluginClassName - net.corda.jmeter.CashIssuerPlugin + + notaryName + O=Perf-10.155.0.4, OU=Corda, L=London, C=GB = - net.corda.jmeter.CordaRPCSampler + com.r3.corda.jmeter.CashIssueSampler - - false - - saveConfig - - - true - true - true - - true - true - true - true - false - true - true - false - false - false - true - false - false - false - true - 0 - true - true - true - true - true - - - - - false @@ -168,6 +132,42 @@ + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + true diff --git a/tools/jmeter/src/main/resources/jmeter.properties b/tools/jmeter/src/main/resources/jmeter.properties index c4d001f71d..e4463eb2f7 100644 --- a/tools/jmeter/src/main/resources/jmeter.properties +++ b/tools/jmeter/src/main/resources/jmeter.properties @@ -238,7 +238,7 @@ gui.quick_9=ViewResultsFullVisualizer #--------------------------------------------------------------------------- # Remote Hosts - comma delimited -remote_hosts=127.0.0.1:20099 +remote_hosts=127.0.0.1:20100,127.0.0.1:20101,127.0.0.1:20102,127.0.0.1:20103,127.0.0.1:20104 #remote_hosts=localhost:1099,localhost:2010 # RMI port to be used by the server (must start rmiregistry with same port) From 24ef98e5c416aaf273b39edb77229f7ef675a3f0 Mon Sep 17 00:00:00 2001 From: "rick.parker" Date: Wed, 15 Nov 2017 13:30:56 +0000 Subject: [PATCH 11/37] Add some comments and clean up. --- tools/jmeter/build.gradle | 13 ++++++++++++- .../jmeter/{FlowSampler.kt => BaseFlowSampler.kt} | 6 ++++-- .../src/main/kotlin/com/r3/corda/jmeter/Samplers.kt | 11 +++++++++-- .../src/main/kotlin/com/r3/corda/jmeter/Ssh.kt | 7 ++++--- 4 files changed, 29 insertions(+), 8 deletions(-) rename tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/{FlowSampler.kt => BaseFlowSampler.kt} (95%) diff --git a/tools/jmeter/build.gradle b/tools/jmeter/build.gradle index 0f43927b39..9b483ac9f2 100644 --- a/tools/jmeter/build.gradle +++ b/tools/jmeter/build.gradle @@ -31,6 +31,7 @@ dependencies { runtime group: 'org.apache.jmeter', name: 'jorphan', version: "$jmVersion" } +// Run JMeter as server process/agent on current host. task(runServer, dependsOn: 'classes', type: JavaExec) { classpath = sourceSets.main.runtimeClasspath main = 'com.r3.corda.jmeter.Launcher' @@ -43,6 +44,7 @@ task(runServer, dependsOn: 'classes', type: JavaExec) { "-s" ] } +// Just start ssh tunnels, without JMeter. task(runSsh, dependsOn: 'classes', type: JavaExec) { classpath = sourceSets.main.runtimeClasspath main = 'com.r3.corda.jmeter.Ssh' @@ -52,6 +54,12 @@ task(runSsh, dependsOn: 'classes', type: JavaExec) { standardInput = System.in } +// Run JMeter (by default the UI). +// Extra args can be passed by setting jmeterArgs (e.g. -n to run in non-UI mode). +// e.g. .gradlew tools:jmeter:run -PjmeterArgs="['-n']" +// ssh tunnels will be built from local host to remote hosts by specifying jmeterHosts +// e.g. ./gradlew tools:jmeter:run -PjmeterHosts="['perf-notary.corda.r3cev.com', 'perf-node-1.corda.r3cev.com']" +// Each host is paired with local host ports listed in remote_hosts of jmeter.properties run { systemProperty "search_paths", project(':tools:jmeter').configurations.runtime.files.join(";") systemProperty "java.rmi.server.hostname", "localhost" @@ -59,6 +67,9 @@ run { // If you want to debug: jvmArgs += "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005" args+= [ "-p", sourceSets.main.resources.getSrcDirs().first().getPath()+"/jmeter.properties", "-d", sourceSets.main.resources.getSrcDirs().first().getPath() ] + if ( project.hasProperty("jmeterArgs") ) { + args+= Eval.me(jmeterHosts) + } if ( project.hasProperty("jmeterHosts") ) { args+= "-Xssh" args+= Eval.me(jmeterHosts) @@ -76,7 +87,7 @@ jar { } // For building a runnable jar with no other dependencies for remote JMeter slave server, that has Corda code on classpath. -// Run with: java -jar corda-jmeter-.jar +// Run with: java -jar jmeter-corda-.jar // No additional args required but will be passed if specified. task buildJMeterJAR(type: FatCapsule, dependsOn: 'jar') { applicationClass 'com.r3.corda.jmeter.Launcher' diff --git a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/FlowSampler.kt b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/BaseFlowSampler.kt similarity index 95% rename from tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/FlowSampler.kt rename to tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/BaseFlowSampler.kt index 206937da97..34b17e4901 100644 --- a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/FlowSampler.kt +++ b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/BaseFlowSampler.kt @@ -11,8 +11,10 @@ import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext import org.apache.jmeter.samplers.SampleResult - -abstract class FlowSampler() : AbstractJavaSamplerClient() { +/** + * Do most of the work for firing flow start requests via RPC at a Corda node. + */ +abstract class BaseFlowSampler() : AbstractJavaSamplerClient() { companion object { val host = Argument("host", "localhost", "", "The remote network address (hostname or IP address) to connect to for RPC.") val port = Argument("port", "10000", "", "The remote port to connect to for RPC.") diff --git a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt index e5fc610e2f..e23cc399cc 100644 --- a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt +++ b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt @@ -9,8 +9,10 @@ import net.corda.finance.flows.CashIssueFlow import org.apache.jmeter.config.Argument import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext - -abstract class AbstractSampler : FlowSampler() { +/** + * A base sampler that looks up identities via RPC ready for starting flows, to be extended and specialised as required. + */ +abstract class AbstractSampler : BaseFlowSampler() { lateinit var notaryIdentity: Party companion object JMeterProperties { @@ -26,6 +28,11 @@ abstract class AbstractSampler : FlowSampler() { } } +/** + * A sampler for calling CashIssueFlow. + * + * TODO: add more configurable parameters (reference, amount etc) if there is a requirement. + */ class CashIssueSampler : AbstractSampler() { override val additionalArgs: Set = setOf(notary) diff --git a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Ssh.kt b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Ssh.kt index 3c4a38391a..93cc88cd7c 100644 --- a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Ssh.kt +++ b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Ssh.kt @@ -10,7 +10,9 @@ import java.io.BufferedReader import java.io.InputStreamReader import java.util.* - +/** + * Creates SSH tunnels for remote controlling SSH servers/agents from the local host (via UI or headless). + */ class Ssh { companion object { val log = LoggerFactory.getLogger(this::class.java) @@ -28,7 +30,7 @@ class Ssh { val jmeterProps = loadProps("/jmeter.properties") // The port the JMeter remote agents call back to on this client host. val clientRmiLocalPort = jmeterProps.getProperty("client.rmi.localport").toInt() - // TODO: Where is this value used? Just on the remote agent to set up the RMI registry? + // Remote RMI registry port. val serverRmiPort = jmeterProps.getProperty("server.rmi.port", "1099").toInt() // Where JMeter driver will try to connect for remote agents (should all be localhost so can ssh tunnel). @@ -47,7 +49,6 @@ class Ssh { val session = connectToHost(jsch, remoteHost, userName) sessions += session - // TODO: maybe check the local host is actually "localhost"? // For tunnelling the RMI registry on the remote agent // ssh ${remoteHostAndPort.host} -L 0.0.0.0:${localHostAndPort.port}:localhost:$serverRmiPort -N createOutboundTunnel(session, NetworkHostAndPort("0.0.0.0", localHostAndPort.port), NetworkHostAndPort("localhost", serverRmiPort)) From e67794645393206bdca2f1219049771edcc33cf6 Mon Sep 17 00:00:00 2001 From: "rick.parker" Date: Wed, 15 Nov 2017 15:16:14 +0000 Subject: [PATCH 12/37] Add some comments and clean up. --- .../jmeter/src/main/kotlin/com/r3/corda/jmeter/Launcher.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Launcher.kt b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Launcher.kt index b45302471f..d1ebfcca23 100644 --- a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Launcher.kt +++ b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Launcher.kt @@ -8,6 +8,11 @@ import java.nio.file.Files import java.nio.file.Paths import kotlin.streams.asSequence +/** + * A wrapper around JMeter to make it run without having a JMeter download installed locally. One mode is used for + * running on a remote cluster using an all-in-one bundle JAR using Capsule. The other is just used to run based on current + * classpath, but with optional SSH tunnelling logic automatically invoked. + */ class Launcher { companion object { @JvmStatic @@ -50,6 +55,8 @@ class Launcher { } private fun maybeOpenSshTunnels(args: Array): Array { + // We trim the args at the point "-Xssh" appears in the array of args. Anything after that is a host to + // SSH tunnel to. var index = 0 for (arg in args) { if (arg == "-Xssh") { From 5c64917f18bb6666a5610ce5975429b746f85aba Mon Sep 17 00:00:00 2001 From: "rick.parker" Date: Fri, 17 Nov 2017 17:38:18 +0000 Subject: [PATCH 13/37] README and fixes. --- tools/jmeter/build.gradle | 2 +- .../main/kotlin/com/r3/corda/jmeter/README.md | 48 +++++++++++++++++++ .../main/resources/Example Flow Request.jmx | 2 +- 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/README.md diff --git a/tools/jmeter/build.gradle b/tools/jmeter/build.gradle index 9b483ac9f2..75c556471b 100644 --- a/tools/jmeter/build.gradle +++ b/tools/jmeter/build.gradle @@ -56,7 +56,7 @@ task(runSsh, dependsOn: 'classes', type: JavaExec) { // Run JMeter (by default the UI). // Extra args can be passed by setting jmeterArgs (e.g. -n to run in non-UI mode). -// e.g. .gradlew tools:jmeter:run -PjmeterArgs="['-n']" +// e.g. ./gradlew tools:jmeter:run -PjmeterArgs="['-n']" // ssh tunnels will be built from local host to remote hosts by specifying jmeterHosts // e.g. ./gradlew tools:jmeter:run -PjmeterHosts="['perf-notary.corda.r3cev.com', 'perf-node-1.corda.r3cev.com']" // Each host is paired with local host ports listed in remote_hosts of jmeter.properties diff --git a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/README.md b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/README.md new file mode 100644 index 0000000000..7166f8678f --- /dev/null +++ b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/README.md @@ -0,0 +1,48 @@ +This module contains gradle tasks to make running the JMeter (http://jmeter.apache.org) +load generation tool against Corda nodes much easier and more useful. It does this by +providing a simple way to launch JMeter with the actual JMeter install coming +from downloaded dependencies, and by providing some Samplers that interact with +the Corda node via RPC. + +To run up the JMeter UI, using the jmeter.properties in the resources folder, +type the following: + +./gradlew tools:jmeter:run + +You can then open the example script in "Example Flow Properties.jmx" via the File -> Open menu option. You need to +configure the host, ports, user name and password in the Java Sampler that correspond to your chosen target Corda node. +Simply running from the UI will result in the RPC client running inside the UI JVM. + +If you wish to pass additional arguments to JMeter, you can do this: + +./gradlew tools:jmeter:run -PjmeterArgs="['-n', '-Ljmeter.engine=DEBUG']" + +The intention is to run against a remote Corda node or nodes, hosted on servers rather than desktops. To +this end, we leverage the JMeter ability to run remote agents that actually execute the tests, with these +reporting results back to the UI (or headless process if you so desire - e.g. for automated benchmarks). This is +supplemented with some additional convenience of automatically creating ssh tunnels to the remote nodes +(we don't want the JMeter ports open to the internet) in coordination with the jmeter.properties. +The remote agents then run close to the nodes, so the latency of RPC calls is minimised. + +A Capsule (http://www.capsule.io) based launchable JAR is created that can be run with the simple command line + +java -jar jmeter-corda-.jar + +Embedded in the JAR is all of the corda code for flows and RPC, as well as the jmeter.propeties. This +JAR will also include a properties file based on the hostname in the JMeter configuration, +so we allocate different SSH tunneled port numbers this way. + +To launch JMeter with the tunnels automatically created: + +./gradlew tools:jmeter:run -PjmeterHosts="['hostname1', 'hostname2']" + +The list of hostnames should be of at least length one, with a maximum equal to the length of the remote_hosts +option in jmeter.properties. We effectively "zip" together the hostnames and that list to build the SSH tunnels. +The remote_hosts property helps define the ports (the hosts should always be local) used +for each host listed in jmeterHosts. Some additional ports are also opened based on some other +parts of the configuration to access the RMI registry and to allow return traffic +from remote agents. + +The SSH tunnels can be started independently with: + +./gradlew tools:jmeter:runSsh -PjmeterHosts="['hostname1', 'hostname2']" \ No newline at end of file diff --git a/tools/jmeter/src/main/resources/Example Flow Request.jmx b/tools/jmeter/src/main/resources/Example Flow Request.jmx index 257573d05e..6a2d25cfcf 100644 --- a/tools/jmeter/src/main/resources/Example Flow Request.jmx +++ b/tools/jmeter/src/main/resources/Example Flow Request.jmx @@ -46,7 +46,7 @@ password - corda_is_awesome + Password Here = From a84e9fdb2b443b39964639e82dcf66d35116543b Mon Sep 17 00:00:00 2001 From: "rick.parker" Date: Mon, 20 Nov 2017 17:09:25 +0000 Subject: [PATCH 14/37] Redirect search_paths into a file since it so long and doesn't work on the command line in Windows. --- tools/jmeter/build.gradle | 11 ++++++++--- .../src/main/kotlin/com/r3/corda/jmeter/Launcher.kt | 3 +++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/tools/jmeter/build.gradle b/tools/jmeter/build.gradle index 75c556471b..8a111b9f6e 100644 --- a/tools/jmeter/build.gradle +++ b/tools/jmeter/build.gradle @@ -35,8 +35,10 @@ dependencies { task(runServer, dependsOn: 'classes', type: JavaExec) { classpath = sourceSets.main.runtimeClasspath main = 'com.r3.corda.jmeter.Launcher' - systemProperty "search_paths", project(':tools:jmeter').configurations.runtime.files.join(";") - systemProperty "java.rmi.server.hostname", "0.0.0.0" + def file = new File("$rootDir/build/search_paths.txt") + file.createNewFile() + file.text = "${project(':tools:jmeter').configurations.runtime.files.join(";")}" + systemProperty "search_paths_file", file.toString() systemProperty "java.rmi.server.hostname", "0.0.0.0" systemProperty "jmeter.home", sourceSets.main.resources.getSrcDirs().first().getPath() // If you want to debug: jvmArgs += "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005" args+= [ "-p", sourceSets.main.resources.getSrcDirs().first().getPath()+"/jmeter.properties", @@ -61,7 +63,10 @@ task(runSsh, dependsOn: 'classes', type: JavaExec) { // e.g. ./gradlew tools:jmeter:run -PjmeterHosts="['perf-notary.corda.r3cev.com', 'perf-node-1.corda.r3cev.com']" // Each host is paired with local host ports listed in remote_hosts of jmeter.properties run { - systemProperty "search_paths", project(':tools:jmeter').configurations.runtime.files.join(";") + def file = new File("$rootDir/build/search_paths.txt") + file.createNewFile() + file.text = "${project(':tools:jmeter').configurations.runtime.files.join(";")}" + systemProperty "search_paths_file", file.toString() systemProperty "java.rmi.server.hostname", "localhost" systemProperty "jmeter.home", sourceSets.main.resources.getSrcDirs().first().getPath() // If you want to debug: jvmArgs += "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005" diff --git a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Launcher.kt b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Launcher.kt index d1ebfcca23..f4695cebfa 100644 --- a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Launcher.kt +++ b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Launcher.kt @@ -50,6 +50,9 @@ class Launcher { } jmeter.start(arrayOf("-s", "-p", (capsuleDirPath / "jmeter.properties").toString()) + extraArgs + args) } else { + val searchPath = Files.readAllLines(Paths.get(System.getProperty("search_paths_file"))).first() + logger.info("search_paths = $searchPath") + System.setProperty("search_paths", searchPath) jmeter.start(maybeOpenSshTunnels(args)) } } From 39ab188e1b46653ad09f9dbd8816c87a991e0de3 Mon Sep 17 00:00:00 2001 From: "rick.parker" Date: Tue, 31 Oct 2017 17:22:58 +0000 Subject: [PATCH 15/37] First working version of RPC & JMeter --- .../net/corda/jmeter/CordaRPCSampler.kt | 83 ++++++++ .../main/kotlin/net/corda/jmeter/Launcher.kt | 13 ++ .../net/corda/jmeter/TraderDemoPlugins.kt | 47 +++++ .../src/main/resources/Java Request.jmx | 177 ++++++++++++++++++ 4 files changed, 320 insertions(+) create mode 100644 tools/jmeter/src/main/kotlin/net/corda/jmeter/CordaRPCSampler.kt create mode 100644 tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt create mode 100644 tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt create mode 100644 tools/jmeter/src/main/resources/Java Request.jmx diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/CordaRPCSampler.kt b/tools/jmeter/src/main/kotlin/net/corda/jmeter/CordaRPCSampler.kt new file mode 100644 index 0000000000..b6d385fec1 --- /dev/null +++ b/tools/jmeter/src/main/kotlin/net/corda/jmeter/CordaRPCSampler.kt @@ -0,0 +1,83 @@ +package net.corda.jmeter + +import net.corda.client.rpc.CordaRPCClient +import net.corda.client.rpc.CordaRPCConnection +import net.corda.core.flows.FlowLogic +import net.corda.core.messaging.CordaRPCOps +import net.corda.core.utilities.NetworkHostAndPort +import org.apache.jmeter.config.Argument +import org.apache.jmeter.config.Arguments +import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient +import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext +import org.apache.jmeter.samplers.SampleResult + + +class CordaRPCSampler() : AbstractJavaSamplerClient() { + companion object { + val host = Argument("host", "localhost", "", "The remote network address (hostname or IP address) to connect to for RPC.") + val port = Argument("port", "10000", "", "The remote port to connect to for RPC.") + val username = Argument("username", "corda", "", "The RPC user to connect to connect as.") + val password = Argument("password", "corda_is_awesome", "", "The password for the RPC user.") + val className = Argument("pluginClassName", "", "", "The class name of the implementation of ${CordaRPCSampler.Plugin::class.java}.") + + val allArgs = setOf(host, port, username, password, className) + } + + var rpcClient: CordaRPCClient? = null + var rpcConnection: CordaRPCConnection? = null + var rpcProxy: CordaRPCOps? = null + var plugin: Plugin? = null + + override fun getDefaultParameters(): Arguments { + // Add copies of all args, since they seem to be mutable. + return Arguments().apply { for(arg in allArgs) { addArgument(arg.clone() as Argument) } } + } + + override fun setupTest(context: JavaSamplerContext) { + super.setupTest(context) + rpcClient = CordaRPCClient(NetworkHostAndPort(context.getParameter(host.name), context.getIntParameter(port.name))) + rpcConnection = rpcClient!!.start(context.getParameter(username.name), context.getParameter(password.name)) + rpcProxy = rpcConnection!!.proxy + plugin = Class.forName(context.getParameter(className.name)).newInstance() as Plugin + plugin!!.setupTest(rpcProxy!!, context) + } + + override fun runTest(context: JavaSamplerContext): SampleResult { + val flowInvoke = plugin!!.createFlowInvoke(rpcProxy!!, context) + val result = SampleResult() + result.sampleStart() + val handle = rpcProxy!!.startFlowDynamic(flowInvoke!!.flowLogicClass, *(flowInvoke!!.args)) + result.sampleLabel = handle.id.toString() + result.latencyEnd() + try { + val flowResult = handle.returnValue.get() + result.sampleEnd() + return result.apply { + isSuccessful = true + } + } catch(e: Exception) { + result.sampleEnd() + return result.apply { + isSuccessful = false + } + } + } + + override fun teardownTest(context: JavaSamplerContext) { + plugin!!.teardownTest(rpcProxy!!, context) + plugin = null + rpcProxy = null + rpcConnection!!.close() + rpcConnection = null + rpcClient = null + super.teardownTest(context) + } + + interface Plugin { + fun setupTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) + fun createFlowInvoke(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext): FlowInvoke<*> + fun teardownTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) + } + + class FlowInvoke>(val flowLogicClass: Class, val args: Array) +} \ No newline at end of file diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt new file mode 100644 index 0000000000..47787db764 --- /dev/null +++ b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt @@ -0,0 +1,13 @@ +package net.corda.jmeter + +import org.apache.jmeter.JMeter + +class Launcher { + companion object { + @JvmStatic + fun main(args: Array) { + val jmeter = JMeter() + jmeter.start(args) + } + } +} \ No newline at end of file diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt b/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt new file mode 100644 index 0000000000..1e91443840 --- /dev/null +++ b/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt @@ -0,0 +1,47 @@ +package net.corda.jmeter + +import net.corda.core.identity.Party +import net.corda.core.messaging.CordaRPCOps +import net.corda.core.messaging.startFlow +import net.corda.core.utilities.OpaqueBytes +import net.corda.core.utilities.getOrThrow +import net.corda.finance.DOLLARS +import net.corda.finance.flows.CashIssueFlow +import net.corda.jmeter.CordaRPCSampler.FlowInvoke +import net.corda.testing.DUMMY_BANK_A +import net.corda.testing.DUMMY_BANK_B +import net.corda.testing.contracts.calculateRandomlySizedAmounts +import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext +import java.util.* + + +abstract class AsbtractTraderDemoPlugin : CordaRPCSampler.Plugin { + + lateinit var buyer: Party + lateinit var seller: Party + lateinit var notary: Party + + protected fun getIdentities(rpc: CordaRPCOps) { + buyer = rpc.wellKnownPartyFromX500Name(DUMMY_BANK_A.name) ?: throw IllegalStateException("Don't know ${DUMMY_BANK_A.name}") + seller = rpc.wellKnownPartyFromX500Name(DUMMY_BANK_B.name) ?: throw IllegalStateException("Don't know ${DUMMY_BANK_B.name}") + notary = rpc.notaryIdentities().first() + } + +} + +class CashIssuerPlugin : AsbtractTraderDemoPlugin() { + override fun setupTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) { + getIdentities(rpcProxy) + } + + override fun teardownTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) { + } + + override fun createFlowInvoke(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext): FlowInvoke { + val amount = 1_100_000_000_000.DOLLARS + //val amounts = calculateRandomlySizedAmounts(amount, 3, 10, Random()) + //rpc.startFlow(net.corda.finance.flows::CashIssueFlow, amount, OpaqueBytes.of(1), notary).returnValue.getOrThrow() + return FlowInvoke(CashIssueFlow::class.java, arrayOf(amount, OpaqueBytes.of(1), notary)) + } + +} \ No newline at end of file diff --git a/tools/jmeter/src/main/resources/Java Request.jmx b/tools/jmeter/src/main/resources/Java Request.jmx new file mode 100644 index 0000000000..974d09bfac --- /dev/null +++ b/tools/jmeter/src/main/resources/Java Request.jmx @@ -0,0 +1,177 @@ + + + + + + false + false + + + + + + + + continue + + false + 1000 + + 2 + + 1509455820000 + 1509455820000 + false + + + + + + + + + host + localhost + = + + + port + 10012 + = + + + username + demo + = + + + password + demo + = + + + pluginClassName + net.corda.jmeter.CashIssuerPlugin + = + + + + net.corda.jmeter.CordaRPCSampler + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + true + + + + From 18cca32d112111bd3de7db7eae0f49b1e37af798 Mon Sep 17 00:00:00 2001 From: "rick.parker" Date: Thu, 2 Nov 2017 11:44:27 +0000 Subject: [PATCH 16/37] Remote JMeter working from single JAR. --- .../kotlin/net/corda/jmeter/TraderDemoPlugins.kt | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt b/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt index 1e91443840..4bfd9a6326 100644 --- a/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt +++ b/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt @@ -1,18 +1,13 @@ package net.corda.jmeter +import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.messaging.CordaRPCOps -import net.corda.core.messaging.startFlow import net.corda.core.utilities.OpaqueBytes -import net.corda.core.utilities.getOrThrow import net.corda.finance.DOLLARS import net.corda.finance.flows.CashIssueFlow import net.corda.jmeter.CordaRPCSampler.FlowInvoke -import net.corda.testing.DUMMY_BANK_A -import net.corda.testing.DUMMY_BANK_B -import net.corda.testing.contracts.calculateRandomlySizedAmounts import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext -import java.util.* abstract class AsbtractTraderDemoPlugin : CordaRPCSampler.Plugin { @@ -21,12 +16,14 @@ abstract class AsbtractTraderDemoPlugin : CordaRPCSampler.Plugin { lateinit var seller: Party lateinit var notary: Party + val bankA = CordaX500Name(organisation = "Bank A", locality = "London", country = "GB") + val bankB = CordaX500Name(organisation = "Bank B", locality = "New York", country = "US") + protected fun getIdentities(rpc: CordaRPCOps) { - buyer = rpc.wellKnownPartyFromX500Name(DUMMY_BANK_A.name) ?: throw IllegalStateException("Don't know ${DUMMY_BANK_A.name}") - seller = rpc.wellKnownPartyFromX500Name(DUMMY_BANK_B.name) ?: throw IllegalStateException("Don't know ${DUMMY_BANK_B.name}") + buyer = rpc.wellKnownPartyFromX500Name(bankA) ?: throw IllegalStateException("Don't know $bankA") + seller = rpc.wellKnownPartyFromX500Name(bankB) ?: throw IllegalStateException("Don't know $bankB") notary = rpc.notaryIdentities().first() } - } class CashIssuerPlugin : AsbtractTraderDemoPlugin() { From 029cc1c2f0399d4e4742540254da816b55381fe2 Mon Sep 17 00:00:00 2001 From: "rick.parker" Date: Thu, 2 Nov 2017 14:58:47 +0000 Subject: [PATCH 17/37] Some clean up. Remote slave via capsule is working. --- .../src/main/kotlin/net/corda/jmeter/Launcher.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt index 47787db764..f06ce17a86 100644 --- a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt +++ b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt @@ -1,13 +1,23 @@ package net.corda.jmeter import org.apache.jmeter.JMeter +import org.slf4j.LoggerFactory class Launcher { companion object { @JvmStatic fun main(args: Array) { + val logger = LoggerFactory.getLogger(this::class.java) val jmeter = JMeter() - jmeter.start(args) + val capsuleDir = System.getProperty("capsule.dir") + if (capsuleDir != null) { + // We are running under Capsule, so assume we want a JMeter distributed server to be controlled from + // elsewhere. + logger.info("Starting JMeter in server mode from $capsuleDir") + jmeter.start(arrayOf("-s", "-d", capsuleDir, "-p", "$capsuleDir/jmeter.properties") + args) + } else { + jmeter.start(args) + } } } } \ No newline at end of file From 3b565f52f763340ddea9c238a08f30fe1037d10d Mon Sep 17 00:00:00 2001 From: "rick.parker" Date: Thu, 2 Nov 2017 15:54:40 +0000 Subject: [PATCH 18/37] Full config of capsule launched JMeter server (was missing functions previously). --- .../main/kotlin/net/corda/jmeter/Launcher.kt | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt index f06ce17a86..c26dae07a1 100644 --- a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt +++ b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt @@ -1,7 +1,11 @@ package net.corda.jmeter +import net.corda.core.internal.div import org.apache.jmeter.JMeter import org.slf4j.LoggerFactory +import java.nio.file.Files +import java.nio.file.Paths +import kotlin.streams.asSequence class Launcher { companion object { @@ -11,10 +15,22 @@ class Launcher { val jmeter = JMeter() val capsuleDir = System.getProperty("capsule.dir") if (capsuleDir != null) { - // We are running under Capsule, so assume we want a JMeter distributed server to be controlled from + // We are running under Capsule, so assume we want a JMeter slave server to be controlled from // elsewhere. logger.info("Starting JMeter in server mode from $capsuleDir") - jmeter.start(arrayOf("-s", "-d", capsuleDir, "-p", "$capsuleDir/jmeter.properties") + args) + // Add all JMeter and Corda jars onto the JMeter search_paths + val searchPath = Files.list(Paths.get(capsuleDir)).asSequence().filter { + val filename = it.fileName.toString() + filename.endsWith(".jar") && (filename.contains("corda") || filename.contains("jmeter", true)) + }.joinToString(";") + logger.info("search_paths = $searchPath") + System.setProperty("search_paths", searchPath) + // Set the JMeter home as a property rather than command line arg, due to inconsistent code in JMeter. + System.setProperty("jmeter.home", capsuleDir) + // Create two dirs that JMeter expects, if they don't already exist. + Files.createDirectories(Paths.get(capsuleDir) / "lib" / "ext") + Files.createDirectories(Paths.get(capsuleDir) / "lib" / "junit") + jmeter.start(arrayOf("-s", "-p", "$capsuleDir/jmeter.properties") + args) } else { jmeter.start(args) } From 766c0b23d21e9ccba9043d48a132e353a0ebe354 Mon Sep 17 00:00:00 2001 From: "rick.parker" Date: Fri, 10 Nov 2017 08:33:55 +0000 Subject: [PATCH 19/37] SSH tunnelling utility. Property files per remote host. --- .../main/kotlin/net/corda/jmeter/Launcher.kt | 20 +++- .../src/main/kotlin/net/corda/jmeter/Ssh.kt | 102 ++++++++++++++++++ .../src/main/resources/Java Request.jmx | 4 +- 3 files changed, 120 insertions(+), 6 deletions(-) create mode 100644 tools/jmeter/src/main/kotlin/net/corda/jmeter/Ssh.kt diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt index c26dae07a1..a108da4b89 100644 --- a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt +++ b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt @@ -3,6 +3,7 @@ package net.corda.jmeter import net.corda.core.internal.div import org.apache.jmeter.JMeter import org.slf4j.LoggerFactory +import java.net.InetAddress import java.nio.file.Files import java.nio.file.Paths import kotlin.streams.asSequence @@ -18,8 +19,9 @@ class Launcher { // We are running under Capsule, so assume we want a JMeter slave server to be controlled from // elsewhere. logger.info("Starting JMeter in server mode from $capsuleDir") + val capsuleDirPath = Paths.get(capsuleDir) // Add all JMeter and Corda jars onto the JMeter search_paths - val searchPath = Files.list(Paths.get(capsuleDir)).asSequence().filter { + val searchPath = Files.list(capsuleDirPath).asSequence().filter { val filename = it.fileName.toString() filename.endsWith(".jar") && (filename.contains("corda") || filename.contains("jmeter", true)) }.joinToString(";") @@ -28,9 +30,19 @@ class Launcher { // Set the JMeter home as a property rather than command line arg, due to inconsistent code in JMeter. System.setProperty("jmeter.home", capsuleDir) // Create two dirs that JMeter expects, if they don't already exist. - Files.createDirectories(Paths.get(capsuleDir) / "lib" / "ext") - Files.createDirectories(Paths.get(capsuleDir) / "lib" / "junit") - jmeter.start(arrayOf("-s", "-p", "$capsuleDir/jmeter.properties") + args) + Files.createDirectories(capsuleDirPath / "lib" / "ext") + Files.createDirectories(capsuleDirPath / "lib" / "junit") + // Now see if we have a hostname specific property file, and if so, add it. + val hostName = InetAddress.getLocalHost().hostName + val hostSpecificConfigFile = capsuleDirPath / "$hostName.properties" + logger.info("Attempting to use host-specific properties file $hostSpecificConfigFile") + val extraArgs = if (Files.exists(hostSpecificConfigFile)) { + logger.info("Found host-specific properties file") + arrayOf("-q", hostSpecificConfigFile.toString()) + } else { + emptyArray() + } + jmeter.start(arrayOf("-s", "-p", (capsuleDirPath / "jmeter.properties").toString()) + extraArgs + args) } else { jmeter.start(args) } diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Ssh.kt b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Ssh.kt new file mode 100644 index 0000000000..74a0fa1036 --- /dev/null +++ b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Ssh.kt @@ -0,0 +1,102 @@ +package net.corda.jmeter + +import com.jcraft.jsch.JSch +import com.jcraft.jsch.Session +import net.corda.core.utilities.NetworkHostAndPort +import net.corda.loadtest.setupJSchWithSshAgent +import org.slf4j.LoggerFactory +import java.io.BufferedReader +import java.io.InputStreamReader +import java.util.* + + +class Ssh { + companion object { + val log = LoggerFactory.getLogger(this::class.java) + + @JvmStatic + fun main(args: Array) { + val userName = System.getProperty("user.name") + val jsch = setupJSchWithSshAgent() + val sessions = mutableListOf() + + // Read jmeter.properties + // For each host:port combo, map them to hosts from command line + + val jmeterProps = loadProps("/jmeter.properties") + // The port the JMeter remote agents call back to on this client host. + val clientRmiLocalPort = jmeterProps.getProperty("client.rmi.localport").toInt() + // TODO: Where is this value used? Just on the remote agent to set up the RMI registry? + val serverRmiPort = jmeterProps.getProperty("server.rmi.port", "1099").toInt() + + // Where JMeter driver will try to connect for remote agents (should all be localhost so can ssh tunnel). + val localHostsAndPorts = jmeterProps.getProperty("remote_hosts", "").split(',').map { it.trim() } + args.zip(localHostsAndPorts) { remoteHost, localHostAndPortString -> + // Actual remote host and port we will tunnel to. + log.info("Creating tunnels for $remoteHost") + val localHostAndPort = NetworkHostAndPort.parse(localHostAndPortString) + + // For the remote host, load their specific property file, since it specifies remote RMI server port + val unqualifiedHostName = remoteHost.substringBefore('.') + val hostProps = loadProps("/$unqualifiedHostName.properties") + + val serverRmiLocalPort = hostProps.getProperty("server.rmi.localport", jmeterProps.getProperty("server.rmi.localport")).toInt() + + val session = connectToHost(jsch, remoteHost, userName) + sessions += session + + // TODO: maybe check the local host is actually "localhost"? + // For tunnelling the RMI registry on the remote agent + // ssh ${remoteHostAndPort.host} -L 0.0.0.0:${localHostAndPort.port}:localhost:$serverRmiPort -N + createOutboundTunnel(session, NetworkHostAndPort("0.0.0.0", localHostAndPort.port), NetworkHostAndPort("localhost", serverRmiPort)) + + // For tunnelling the actual connection to the remote agent + // ssh ${remoteHostAndPort.host} -L 0.0.0.0:$serverRmiLocalPort:localhost:$serverRmiLocalPort -N + createOutboundTunnel(session, NetworkHostAndPort("0.0.0.0", serverRmiLocalPort), NetworkHostAndPort("localhost", serverRmiLocalPort)) + + // For returning results to the client + // ssh ${remoteHostAndPort.host} -R 0.0.0.0:clientRmiLocalPort:localhost:clientRmiLocalPort -N + createInboundTunnel(session, NetworkHostAndPort("0.0.0.0", clientRmiLocalPort), NetworkHostAndPort("localhost", clientRmiLocalPort)) + } + val input = BufferedReader(InputStreamReader(System.`in`)) + + do { + log.info("Type 'quit' to exit cleanly.") + } while (input.readLine() != "quit") + sessions.forEach { + log.info("Closing tunnels for ${it.host}") + it.disconnect() + } + } + + private fun loadProps(filename: String): Properties { + val props = Properties() + this::class.java.getResourceAsStream(filename).use { + props.load(it) + } + return props + } + + fun connectToHost(jSch: JSch, remoteHost: String, remoteUserName: String): Session { + val session = jSch.getSession(remoteUserName, remoteHost, 22) + // We don't check the host fingerprints because they may change often + session.setConfig("StrictHostKeyChecking", "no") + log.info("Connecting to $remoteHost...") + session.connect() + log.info("Connected to $remoteHost!") + return session + } + + fun createOutboundTunnel(session: Session, local: NetworkHostAndPort, remote: NetworkHostAndPort) { + log.info("Creating outbound tunnel from $local to $remote with ${session.host}...") + session.setPortForwardingL(local.host, local.port, remote.host, remote.port) + log.info("Tunnel created!") + } + + fun createInboundTunnel(session: Session, local: NetworkHostAndPort, remote: NetworkHostAndPort) { + log.info("Creating inbound tunnel from $remote to $local on ${session.host}...") + session.setPortForwardingR(remote.host, remote.port, local.host, local.port) + log.info("Tunnel created!") + } + } +} \ No newline at end of file diff --git a/tools/jmeter/src/main/resources/Java Request.jmx b/tools/jmeter/src/main/resources/Java Request.jmx index 974d09bfac..e0718d0211 100644 --- a/tools/jmeter/src/main/resources/Java Request.jmx +++ b/tools/jmeter/src/main/resources/Java Request.jmx @@ -15,9 +15,9 @@ continue false - 1000 + 10 - 2 + 3 1509455820000 1509455820000 From b7fb87ed832f751f5729fad3fd227990899cf9f7 Mon Sep 17 00:00:00 2001 From: "rick.parker" Date: Fri, 10 Nov 2017 11:40:12 +0000 Subject: [PATCH 20/37] Easy all in one launch of UI + SSH tunnels. --- .../main/kotlin/net/corda/jmeter/Launcher.kt | 16 ++++++++++- .../src/main/kotlin/net/corda/jmeter/Ssh.kt | 27 +++++++++++++------ 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt index a108da4b89..827e76cef4 100644 --- a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt +++ b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt @@ -13,6 +13,7 @@ class Launcher { @JvmStatic fun main(args: Array) { val logger = LoggerFactory.getLogger(this::class.java) + logger.info("Launcher called with ${args.toList()}") val jmeter = JMeter() val capsuleDir = System.getProperty("capsule.dir") if (capsuleDir != null) { @@ -44,8 +45,21 @@ class Launcher { } jmeter.start(arrayOf("-s", "-p", (capsuleDirPath / "jmeter.properties").toString()) + extraArgs + args) } else { - jmeter.start(args) + jmeter.start(maybeOpenSshTunnels(args)) } } + + private fun maybeOpenSshTunnels(args: Array): Array { + var index = 0 + for (arg in args) { + if (arg == "-Xssh") { + // start ssh + Ssh.main(args.copyOfRange(index + 1, args.size), false) + return if (index == 0) emptyArray() else args.copyOfRange(0, index) + } + index++ + } + return args + } } } \ No newline at end of file diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Ssh.kt b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Ssh.kt index 74a0fa1036..47060966ca 100644 --- a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Ssh.kt +++ b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Ssh.kt @@ -4,6 +4,7 @@ import com.jcraft.jsch.JSch import com.jcraft.jsch.Session import net.corda.core.utilities.NetworkHostAndPort import net.corda.loadtest.setupJSchWithSshAgent +import net.corda.nodeapi.internal.addShutdownHook import org.slf4j.LoggerFactory import java.io.BufferedReader import java.io.InputStreamReader @@ -15,7 +16,8 @@ class Ssh { val log = LoggerFactory.getLogger(this::class.java) @JvmStatic - fun main(args: Array) { + @JvmOverloads + fun main(args: Array, wait: Boolean = true) { val userName = System.getProperty("user.name") val jsch = setupJSchWithSshAgent() val sessions = mutableListOf() @@ -58,14 +60,23 @@ class Ssh { // ssh ${remoteHostAndPort.host} -R 0.0.0.0:clientRmiLocalPort:localhost:clientRmiLocalPort -N createInboundTunnel(session, NetworkHostAndPort("0.0.0.0", clientRmiLocalPort), NetworkHostAndPort("localhost", clientRmiLocalPort)) } - val input = BufferedReader(InputStreamReader(System.`in`)) - do { - log.info("Type 'quit' to exit cleanly.") - } while (input.readLine() != "quit") - sessions.forEach { - log.info("Closing tunnels for ${it.host}") - it.disconnect() + if (wait) { + val input = BufferedReader(InputStreamReader(System.`in`)) + do { + log.info("Type 'quit' to exit cleanly.") + } while (input.readLine() != "quit") + sessions.forEach { + log.info("Closing tunnels for ${it.host}") + it.disconnect() + } + } else { + addShutdownHook { + sessions.forEach { + log.info("Closing tunnels for ${it.host}") + it.disconnect() + } + } } } From 6a6d84cce416b42b3ff6511c0012006be1ec0337 Mon Sep 17 00:00:00 2001 From: "rick.parker" Date: Fri, 10 Nov 2017 16:30:07 +0000 Subject: [PATCH 21/37] Comment out parties. --- .../src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt b/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt index 4bfd9a6326..a126140b31 100644 --- a/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt +++ b/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt @@ -20,8 +20,8 @@ abstract class AsbtractTraderDemoPlugin : CordaRPCSampler.Plugin { val bankB = CordaX500Name(organisation = "Bank B", locality = "New York", country = "US") protected fun getIdentities(rpc: CordaRPCOps) { - buyer = rpc.wellKnownPartyFromX500Name(bankA) ?: throw IllegalStateException("Don't know $bankA") - seller = rpc.wellKnownPartyFromX500Name(bankB) ?: throw IllegalStateException("Don't know $bankB") + //buyer = rpc.wellKnownPartyFromX500Name(bankA) ?: throw IllegalStateException("Don't know $bankA") + //seller = rpc.wellKnownPartyFromX500Name(bankB) ?: throw IllegalStateException("Don't know $bankB") notary = rpc.notaryIdentities().first() } } From fa7c67f34b1a4f99a0bda821dd22af18d02ac37a Mon Sep 17 00:00:00 2001 From: "rick.parker" Date: Mon, 13 Nov 2017 11:33:13 +0000 Subject: [PATCH 22/37] Work around for notary. --- .../kotlin/net/corda/jmeter/TraderDemoPlugins.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt b/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt index a126140b31..357a1067f0 100644 --- a/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt +++ b/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt @@ -12,17 +12,19 @@ import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext abstract class AsbtractTraderDemoPlugin : CordaRPCSampler.Plugin { - lateinit var buyer: Party - lateinit var seller: Party + //lateinit var buyer: Party + //lateinit var seller: Party lateinit var notary: Party - val bankA = CordaX500Name(organisation = "Bank A", locality = "London", country = "GB") - val bankB = CordaX500Name(organisation = "Bank B", locality = "New York", country = "US") + //val bankA = CordaX500Name(organisation = "Bank A", locality = "London", country = "GB") + //val bankB = CordaX500Name(organisation = "Bank B", locality = "New York", country = "US") + val node1 = CordaX500Name(commonName = null, state = null, organisation = "Perf-10.155.0.4", organisationUnit = "Corda", locality = "London", country = "GB") protected fun getIdentities(rpc: CordaRPCOps) { //buyer = rpc.wellKnownPartyFromX500Name(bankA) ?: throw IllegalStateException("Don't know $bankA") //seller = rpc.wellKnownPartyFromX500Name(bankB) ?: throw IllegalStateException("Don't know $bankB") - notary = rpc.notaryIdentities().first() + //notary = rpc.notaryIdentities().first() + notary = rpc.wellKnownPartyFromX500Name(node1) ?: throw IllegalStateException("Don't know $node1") } } From 08577510afcb762de21a195648c74b9a9925b9b4 Mon Sep 17 00:00:00 2001 From: "rick.parker" Date: Tue, 14 Nov 2017 13:48:35 +0000 Subject: [PATCH 23/37] Clean up, renaming etc --- .../r3/corda/jmeter/FlowSampler.kt} | 36 ++-- .../kotlin/com/r3/corda/jmeter/Launcher.kt | 10 - .../main/kotlin/com/r3/corda/jmeter/Ssh.kt | 7 +- .../main/kotlin/net/corda/jmeter/Launcher.kt | 65 ------- .../src/main/kotlin/net/corda/jmeter/Ssh.kt | 113 ----------- .../net/corda/jmeter/TraderDemoPlugins.kt | 46 ----- .../main/resources/Example Flow Request.jmx | 2 +- .../src/main/resources/Java Request.jmx | 177 ------------------ 8 files changed, 23 insertions(+), 433 deletions(-) rename tools/jmeter/src/main/kotlin/{net/corda/jmeter/CordaRPCSampler.kt => com/r3/corda/jmeter/FlowSampler.kt} (71%) delete mode 100644 tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt delete mode 100644 tools/jmeter/src/main/kotlin/net/corda/jmeter/Ssh.kt delete mode 100644 tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt delete mode 100644 tools/jmeter/src/main/resources/Java Request.jmx diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/CordaRPCSampler.kt b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/FlowSampler.kt similarity index 71% rename from tools/jmeter/src/main/kotlin/net/corda/jmeter/CordaRPCSampler.kt rename to tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/FlowSampler.kt index b6d385fec1..206937da97 100644 --- a/tools/jmeter/src/main/kotlin/net/corda/jmeter/CordaRPCSampler.kt +++ b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/FlowSampler.kt @@ -1,4 +1,4 @@ -package net.corda.jmeter +package com.r3.corda.jmeter import net.corda.client.rpc.CordaRPCClient import net.corda.client.rpc.CordaRPCConnection @@ -12,25 +12,30 @@ import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext import org.apache.jmeter.samplers.SampleResult -class CordaRPCSampler() : AbstractJavaSamplerClient() { +abstract class FlowSampler() : AbstractJavaSamplerClient() { companion object { val host = Argument("host", "localhost", "", "The remote network address (hostname or IP address) to connect to for RPC.") val port = Argument("port", "10000", "", "The remote port to connect to for RPC.") val username = Argument("username", "corda", "", "The RPC user to connect to connect as.") val password = Argument("password", "corda_is_awesome", "", "The password for the RPC user.") - val className = Argument("pluginClassName", "", "", "The class name of the implementation of ${CordaRPCSampler.Plugin::class.java}.") - val allArgs = setOf(host, port, username, password, className) + val allArgs = setOf(host, port, username, password) } var rpcClient: CordaRPCClient? = null var rpcConnection: CordaRPCConnection? = null var rpcProxy: CordaRPCOps? = null - var plugin: Plugin? = null override fun getDefaultParameters(): Arguments { // Add copies of all args, since they seem to be mutable. - return Arguments().apply { for(arg in allArgs) { addArgument(arg.clone() as Argument) } } + return Arguments().apply { + for (arg in allArgs) { + addArgument(arg.clone() as Argument) + } + for (arg in additionalArgs) { + addArgument(arg.clone() as Argument) + } + } } override fun setupTest(context: JavaSamplerContext) { @@ -38,12 +43,11 @@ class CordaRPCSampler() : AbstractJavaSamplerClient() { rpcClient = CordaRPCClient(NetworkHostAndPort(context.getParameter(host.name), context.getIntParameter(port.name))) rpcConnection = rpcClient!!.start(context.getParameter(username.name), context.getParameter(password.name)) rpcProxy = rpcConnection!!.proxy - plugin = Class.forName(context.getParameter(className.name)).newInstance() as Plugin - plugin!!.setupTest(rpcProxy!!, context) + setupTest(rpcProxy!!, context) } override fun runTest(context: JavaSamplerContext): SampleResult { - val flowInvoke = plugin!!.createFlowInvoke(rpcProxy!!, context) + val flowInvoke = createFlowInvoke(rpcProxy!!, context) val result = SampleResult() result.sampleStart() val handle = rpcProxy!!.startFlowDynamic(flowInvoke!!.flowLogicClass, *(flowInvoke!!.args)) @@ -55,7 +59,7 @@ class CordaRPCSampler() : AbstractJavaSamplerClient() { return result.apply { isSuccessful = true } - } catch(e: Exception) { + } catch (e: Exception) { result.sampleEnd() return result.apply { isSuccessful = false @@ -64,8 +68,7 @@ class CordaRPCSampler() : AbstractJavaSamplerClient() { } override fun teardownTest(context: JavaSamplerContext) { - plugin!!.teardownTest(rpcProxy!!, context) - plugin = null + teardownTest(rpcProxy!!, context) rpcProxy = null rpcConnection!!.close() rpcConnection = null @@ -73,11 +76,10 @@ class CordaRPCSampler() : AbstractJavaSamplerClient() { super.teardownTest(context) } - interface Plugin { - fun setupTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) - fun createFlowInvoke(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext): FlowInvoke<*> - fun teardownTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) - } + abstract val additionalArgs: Set + abstract fun setupTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) + abstract fun createFlowInvoke(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext): FlowInvoke<*> + abstract fun teardownTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) class FlowInvoke>(val flowLogicClass: Class, val args: Array) } \ No newline at end of file diff --git a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Launcher.kt b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Launcher.kt index f4695cebfa..b45302471f 100644 --- a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Launcher.kt +++ b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Launcher.kt @@ -8,11 +8,6 @@ import java.nio.file.Files import java.nio.file.Paths import kotlin.streams.asSequence -/** - * A wrapper around JMeter to make it run without having a JMeter download installed locally. One mode is used for - * running on a remote cluster using an all-in-one bundle JAR using Capsule. The other is just used to run based on current - * classpath, but with optional SSH tunnelling logic automatically invoked. - */ class Launcher { companion object { @JvmStatic @@ -50,16 +45,11 @@ class Launcher { } jmeter.start(arrayOf("-s", "-p", (capsuleDirPath / "jmeter.properties").toString()) + extraArgs + args) } else { - val searchPath = Files.readAllLines(Paths.get(System.getProperty("search_paths_file"))).first() - logger.info("search_paths = $searchPath") - System.setProperty("search_paths", searchPath) jmeter.start(maybeOpenSshTunnels(args)) } } private fun maybeOpenSshTunnels(args: Array): Array { - // We trim the args at the point "-Xssh" appears in the array of args. Anything after that is a host to - // SSH tunnel to. var index = 0 for (arg in args) { if (arg == "-Xssh") { diff --git a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Ssh.kt b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Ssh.kt index 93cc88cd7c..3c4a38391a 100644 --- a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Ssh.kt +++ b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Ssh.kt @@ -10,9 +10,7 @@ import java.io.BufferedReader import java.io.InputStreamReader import java.util.* -/** - * Creates SSH tunnels for remote controlling SSH servers/agents from the local host (via UI or headless). - */ + class Ssh { companion object { val log = LoggerFactory.getLogger(this::class.java) @@ -30,7 +28,7 @@ class Ssh { val jmeterProps = loadProps("/jmeter.properties") // The port the JMeter remote agents call back to on this client host. val clientRmiLocalPort = jmeterProps.getProperty("client.rmi.localport").toInt() - // Remote RMI registry port. + // TODO: Where is this value used? Just on the remote agent to set up the RMI registry? val serverRmiPort = jmeterProps.getProperty("server.rmi.port", "1099").toInt() // Where JMeter driver will try to connect for remote agents (should all be localhost so can ssh tunnel). @@ -49,6 +47,7 @@ class Ssh { val session = connectToHost(jsch, remoteHost, userName) sessions += session + // TODO: maybe check the local host is actually "localhost"? // For tunnelling the RMI registry on the remote agent // ssh ${remoteHostAndPort.host} -L 0.0.0.0:${localHostAndPort.port}:localhost:$serverRmiPort -N createOutboundTunnel(session, NetworkHostAndPort("0.0.0.0", localHostAndPort.port), NetworkHostAndPort("localhost", serverRmiPort)) diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt deleted file mode 100644 index 827e76cef4..0000000000 --- a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Launcher.kt +++ /dev/null @@ -1,65 +0,0 @@ -package net.corda.jmeter - -import net.corda.core.internal.div -import org.apache.jmeter.JMeter -import org.slf4j.LoggerFactory -import java.net.InetAddress -import java.nio.file.Files -import java.nio.file.Paths -import kotlin.streams.asSequence - -class Launcher { - companion object { - @JvmStatic - fun main(args: Array) { - val logger = LoggerFactory.getLogger(this::class.java) - logger.info("Launcher called with ${args.toList()}") - val jmeter = JMeter() - val capsuleDir = System.getProperty("capsule.dir") - if (capsuleDir != null) { - // We are running under Capsule, so assume we want a JMeter slave server to be controlled from - // elsewhere. - logger.info("Starting JMeter in server mode from $capsuleDir") - val capsuleDirPath = Paths.get(capsuleDir) - // Add all JMeter and Corda jars onto the JMeter search_paths - val searchPath = Files.list(capsuleDirPath).asSequence().filter { - val filename = it.fileName.toString() - filename.endsWith(".jar") && (filename.contains("corda") || filename.contains("jmeter", true)) - }.joinToString(";") - logger.info("search_paths = $searchPath") - System.setProperty("search_paths", searchPath) - // Set the JMeter home as a property rather than command line arg, due to inconsistent code in JMeter. - System.setProperty("jmeter.home", capsuleDir) - // Create two dirs that JMeter expects, if they don't already exist. - Files.createDirectories(capsuleDirPath / "lib" / "ext") - Files.createDirectories(capsuleDirPath / "lib" / "junit") - // Now see if we have a hostname specific property file, and if so, add it. - val hostName = InetAddress.getLocalHost().hostName - val hostSpecificConfigFile = capsuleDirPath / "$hostName.properties" - logger.info("Attempting to use host-specific properties file $hostSpecificConfigFile") - val extraArgs = if (Files.exists(hostSpecificConfigFile)) { - logger.info("Found host-specific properties file") - arrayOf("-q", hostSpecificConfigFile.toString()) - } else { - emptyArray() - } - jmeter.start(arrayOf("-s", "-p", (capsuleDirPath / "jmeter.properties").toString()) + extraArgs + args) - } else { - jmeter.start(maybeOpenSshTunnels(args)) - } - } - - private fun maybeOpenSshTunnels(args: Array): Array { - var index = 0 - for (arg in args) { - if (arg == "-Xssh") { - // start ssh - Ssh.main(args.copyOfRange(index + 1, args.size), false) - return if (index == 0) emptyArray() else args.copyOfRange(0, index) - } - index++ - } - return args - } - } -} \ No newline at end of file diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Ssh.kt b/tools/jmeter/src/main/kotlin/net/corda/jmeter/Ssh.kt deleted file mode 100644 index 47060966ca..0000000000 --- a/tools/jmeter/src/main/kotlin/net/corda/jmeter/Ssh.kt +++ /dev/null @@ -1,113 +0,0 @@ -package net.corda.jmeter - -import com.jcraft.jsch.JSch -import com.jcraft.jsch.Session -import net.corda.core.utilities.NetworkHostAndPort -import net.corda.loadtest.setupJSchWithSshAgent -import net.corda.nodeapi.internal.addShutdownHook -import org.slf4j.LoggerFactory -import java.io.BufferedReader -import java.io.InputStreamReader -import java.util.* - - -class Ssh { - companion object { - val log = LoggerFactory.getLogger(this::class.java) - - @JvmStatic - @JvmOverloads - fun main(args: Array, wait: Boolean = true) { - val userName = System.getProperty("user.name") - val jsch = setupJSchWithSshAgent() - val sessions = mutableListOf() - - // Read jmeter.properties - // For each host:port combo, map them to hosts from command line - - val jmeterProps = loadProps("/jmeter.properties") - // The port the JMeter remote agents call back to on this client host. - val clientRmiLocalPort = jmeterProps.getProperty("client.rmi.localport").toInt() - // TODO: Where is this value used? Just on the remote agent to set up the RMI registry? - val serverRmiPort = jmeterProps.getProperty("server.rmi.port", "1099").toInt() - - // Where JMeter driver will try to connect for remote agents (should all be localhost so can ssh tunnel). - val localHostsAndPorts = jmeterProps.getProperty("remote_hosts", "").split(',').map { it.trim() } - args.zip(localHostsAndPorts) { remoteHost, localHostAndPortString -> - // Actual remote host and port we will tunnel to. - log.info("Creating tunnels for $remoteHost") - val localHostAndPort = NetworkHostAndPort.parse(localHostAndPortString) - - // For the remote host, load their specific property file, since it specifies remote RMI server port - val unqualifiedHostName = remoteHost.substringBefore('.') - val hostProps = loadProps("/$unqualifiedHostName.properties") - - val serverRmiLocalPort = hostProps.getProperty("server.rmi.localport", jmeterProps.getProperty("server.rmi.localport")).toInt() - - val session = connectToHost(jsch, remoteHost, userName) - sessions += session - - // TODO: maybe check the local host is actually "localhost"? - // For tunnelling the RMI registry on the remote agent - // ssh ${remoteHostAndPort.host} -L 0.0.0.0:${localHostAndPort.port}:localhost:$serverRmiPort -N - createOutboundTunnel(session, NetworkHostAndPort("0.0.0.0", localHostAndPort.port), NetworkHostAndPort("localhost", serverRmiPort)) - - // For tunnelling the actual connection to the remote agent - // ssh ${remoteHostAndPort.host} -L 0.0.0.0:$serverRmiLocalPort:localhost:$serverRmiLocalPort -N - createOutboundTunnel(session, NetworkHostAndPort("0.0.0.0", serverRmiLocalPort), NetworkHostAndPort("localhost", serverRmiLocalPort)) - - // For returning results to the client - // ssh ${remoteHostAndPort.host} -R 0.0.0.0:clientRmiLocalPort:localhost:clientRmiLocalPort -N - createInboundTunnel(session, NetworkHostAndPort("0.0.0.0", clientRmiLocalPort), NetworkHostAndPort("localhost", clientRmiLocalPort)) - } - - if (wait) { - val input = BufferedReader(InputStreamReader(System.`in`)) - do { - log.info("Type 'quit' to exit cleanly.") - } while (input.readLine() != "quit") - sessions.forEach { - log.info("Closing tunnels for ${it.host}") - it.disconnect() - } - } else { - addShutdownHook { - sessions.forEach { - log.info("Closing tunnels for ${it.host}") - it.disconnect() - } - } - } - } - - private fun loadProps(filename: String): Properties { - val props = Properties() - this::class.java.getResourceAsStream(filename).use { - props.load(it) - } - return props - } - - fun connectToHost(jSch: JSch, remoteHost: String, remoteUserName: String): Session { - val session = jSch.getSession(remoteUserName, remoteHost, 22) - // We don't check the host fingerprints because they may change often - session.setConfig("StrictHostKeyChecking", "no") - log.info("Connecting to $remoteHost...") - session.connect() - log.info("Connected to $remoteHost!") - return session - } - - fun createOutboundTunnel(session: Session, local: NetworkHostAndPort, remote: NetworkHostAndPort) { - log.info("Creating outbound tunnel from $local to $remote with ${session.host}...") - session.setPortForwardingL(local.host, local.port, remote.host, remote.port) - log.info("Tunnel created!") - } - - fun createInboundTunnel(session: Session, local: NetworkHostAndPort, remote: NetworkHostAndPort) { - log.info("Creating inbound tunnel from $remote to $local on ${session.host}...") - session.setPortForwardingR(remote.host, remote.port, local.host, local.port) - log.info("Tunnel created!") - } - } -} \ No newline at end of file diff --git a/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt b/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt deleted file mode 100644 index 357a1067f0..0000000000 --- a/tools/jmeter/src/main/kotlin/net/corda/jmeter/TraderDemoPlugins.kt +++ /dev/null @@ -1,46 +0,0 @@ -package net.corda.jmeter - -import net.corda.core.identity.CordaX500Name -import net.corda.core.identity.Party -import net.corda.core.messaging.CordaRPCOps -import net.corda.core.utilities.OpaqueBytes -import net.corda.finance.DOLLARS -import net.corda.finance.flows.CashIssueFlow -import net.corda.jmeter.CordaRPCSampler.FlowInvoke -import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext - - -abstract class AsbtractTraderDemoPlugin : CordaRPCSampler.Plugin { - - //lateinit var buyer: Party - //lateinit var seller: Party - lateinit var notary: Party - - //val bankA = CordaX500Name(organisation = "Bank A", locality = "London", country = "GB") - //val bankB = CordaX500Name(organisation = "Bank B", locality = "New York", country = "US") - val node1 = CordaX500Name(commonName = null, state = null, organisation = "Perf-10.155.0.4", organisationUnit = "Corda", locality = "London", country = "GB") - - protected fun getIdentities(rpc: CordaRPCOps) { - //buyer = rpc.wellKnownPartyFromX500Name(bankA) ?: throw IllegalStateException("Don't know $bankA") - //seller = rpc.wellKnownPartyFromX500Name(bankB) ?: throw IllegalStateException("Don't know $bankB") - //notary = rpc.notaryIdentities().first() - notary = rpc.wellKnownPartyFromX500Name(node1) ?: throw IllegalStateException("Don't know $node1") - } -} - -class CashIssuerPlugin : AsbtractTraderDemoPlugin() { - override fun setupTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) { - getIdentities(rpcProxy) - } - - override fun teardownTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) { - } - - override fun createFlowInvoke(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext): FlowInvoke { - val amount = 1_100_000_000_000.DOLLARS - //val amounts = calculateRandomlySizedAmounts(amount, 3, 10, Random()) - //rpc.startFlow(net.corda.finance.flows::CashIssueFlow, amount, OpaqueBytes.of(1), notary).returnValue.getOrThrow() - return FlowInvoke(CashIssueFlow::class.java, arrayOf(amount, OpaqueBytes.of(1), notary)) - } - -} \ No newline at end of file diff --git a/tools/jmeter/src/main/resources/Example Flow Request.jmx b/tools/jmeter/src/main/resources/Example Flow Request.jmx index 6a2d25cfcf..257573d05e 100644 --- a/tools/jmeter/src/main/resources/Example Flow Request.jmx +++ b/tools/jmeter/src/main/resources/Example Flow Request.jmx @@ -46,7 +46,7 @@ password - Password Here + corda_is_awesome = diff --git a/tools/jmeter/src/main/resources/Java Request.jmx b/tools/jmeter/src/main/resources/Java Request.jmx deleted file mode 100644 index e0718d0211..0000000000 --- a/tools/jmeter/src/main/resources/Java Request.jmx +++ /dev/null @@ -1,177 +0,0 @@ - - - - - - false - false - - - - - - - - continue - - false - 10 - - 3 - - 1509455820000 - 1509455820000 - false - - - - - - - - - host - localhost - = - - - port - 10012 - = - - - username - demo - = - - - password - demo - = - - - pluginClassName - net.corda.jmeter.CashIssuerPlugin - = - - - - net.corda.jmeter.CordaRPCSampler - - - - - false - - saveConfig - - - true - true - true - - true - true - true - true - false - true - true - false - false - false - true - false - false - false - true - 0 - true - true - true - true - true - - - - - - - false - - saveConfig - - - true - true - true - - true - true - true - true - false - true - true - false - false - false - true - false - false - false - true - 0 - true - true - true - true - true - - - - - - - false - - saveConfig - - - true - true - true - - true - true - true - true - false - true - true - false - false - false - true - false - false - false - true - 0 - true - true - true - true - true - - - - - - - - true - - - - From 1d5d54e0637155345cd14f0eb5b88e9b862c74fa Mon Sep 17 00:00:00 2001 From: "rick.parker" Date: Wed, 15 Nov 2017 13:30:56 +0000 Subject: [PATCH 24/37] Add some comments and clean up. --- .../kotlin/com/r3/corda/jmeter/FlowSampler.kt | 85 ------------------- .../main/kotlin/com/r3/corda/jmeter/Ssh.kt | 7 +- 2 files changed, 4 insertions(+), 88 deletions(-) delete mode 100644 tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/FlowSampler.kt diff --git a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/FlowSampler.kt b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/FlowSampler.kt deleted file mode 100644 index 206937da97..0000000000 --- a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/FlowSampler.kt +++ /dev/null @@ -1,85 +0,0 @@ -package com.r3.corda.jmeter - -import net.corda.client.rpc.CordaRPCClient -import net.corda.client.rpc.CordaRPCConnection -import net.corda.core.flows.FlowLogic -import net.corda.core.messaging.CordaRPCOps -import net.corda.core.utilities.NetworkHostAndPort -import org.apache.jmeter.config.Argument -import org.apache.jmeter.config.Arguments -import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient -import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext -import org.apache.jmeter.samplers.SampleResult - - -abstract class FlowSampler() : AbstractJavaSamplerClient() { - companion object { - val host = Argument("host", "localhost", "", "The remote network address (hostname or IP address) to connect to for RPC.") - val port = Argument("port", "10000", "", "The remote port to connect to for RPC.") - val username = Argument("username", "corda", "", "The RPC user to connect to connect as.") - val password = Argument("password", "corda_is_awesome", "", "The password for the RPC user.") - - val allArgs = setOf(host, port, username, password) - } - - var rpcClient: CordaRPCClient? = null - var rpcConnection: CordaRPCConnection? = null - var rpcProxy: CordaRPCOps? = null - - override fun getDefaultParameters(): Arguments { - // Add copies of all args, since they seem to be mutable. - return Arguments().apply { - for (arg in allArgs) { - addArgument(arg.clone() as Argument) - } - for (arg in additionalArgs) { - addArgument(arg.clone() as Argument) - } - } - } - - override fun setupTest(context: JavaSamplerContext) { - super.setupTest(context) - rpcClient = CordaRPCClient(NetworkHostAndPort(context.getParameter(host.name), context.getIntParameter(port.name))) - rpcConnection = rpcClient!!.start(context.getParameter(username.name), context.getParameter(password.name)) - rpcProxy = rpcConnection!!.proxy - setupTest(rpcProxy!!, context) - } - - override fun runTest(context: JavaSamplerContext): SampleResult { - val flowInvoke = createFlowInvoke(rpcProxy!!, context) - val result = SampleResult() - result.sampleStart() - val handle = rpcProxy!!.startFlowDynamic(flowInvoke!!.flowLogicClass, *(flowInvoke!!.args)) - result.sampleLabel = handle.id.toString() - result.latencyEnd() - try { - val flowResult = handle.returnValue.get() - result.sampleEnd() - return result.apply { - isSuccessful = true - } - } catch (e: Exception) { - result.sampleEnd() - return result.apply { - isSuccessful = false - } - } - } - - override fun teardownTest(context: JavaSamplerContext) { - teardownTest(rpcProxy!!, context) - rpcProxy = null - rpcConnection!!.close() - rpcConnection = null - rpcClient = null - super.teardownTest(context) - } - - abstract val additionalArgs: Set - abstract fun setupTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) - abstract fun createFlowInvoke(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext): FlowInvoke<*> - abstract fun teardownTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) - - class FlowInvoke>(val flowLogicClass: Class, val args: Array) -} \ No newline at end of file diff --git a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Ssh.kt b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Ssh.kt index 3c4a38391a..93cc88cd7c 100644 --- a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Ssh.kt +++ b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Ssh.kt @@ -10,7 +10,9 @@ import java.io.BufferedReader import java.io.InputStreamReader import java.util.* - +/** + * Creates SSH tunnels for remote controlling SSH servers/agents from the local host (via UI or headless). + */ class Ssh { companion object { val log = LoggerFactory.getLogger(this::class.java) @@ -28,7 +30,7 @@ class Ssh { val jmeterProps = loadProps("/jmeter.properties") // The port the JMeter remote agents call back to on this client host. val clientRmiLocalPort = jmeterProps.getProperty("client.rmi.localport").toInt() - // TODO: Where is this value used? Just on the remote agent to set up the RMI registry? + // Remote RMI registry port. val serverRmiPort = jmeterProps.getProperty("server.rmi.port", "1099").toInt() // Where JMeter driver will try to connect for remote agents (should all be localhost so can ssh tunnel). @@ -47,7 +49,6 @@ class Ssh { val session = connectToHost(jsch, remoteHost, userName) sessions += session - // TODO: maybe check the local host is actually "localhost"? // For tunnelling the RMI registry on the remote agent // ssh ${remoteHostAndPort.host} -L 0.0.0.0:${localHostAndPort.port}:localhost:$serverRmiPort -N createOutboundTunnel(session, NetworkHostAndPort("0.0.0.0", localHostAndPort.port), NetworkHostAndPort("localhost", serverRmiPort)) From 6fc981e13d11545f838c23e1ea17d20af5d3107e Mon Sep 17 00:00:00 2001 From: "rick.parker" Date: Wed, 15 Nov 2017 15:16:14 +0000 Subject: [PATCH 25/37] Add some comments and clean up. --- .../jmeter/src/main/kotlin/com/r3/corda/jmeter/Launcher.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Launcher.kt b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Launcher.kt index b45302471f..d1ebfcca23 100644 --- a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Launcher.kt +++ b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Launcher.kt @@ -8,6 +8,11 @@ import java.nio.file.Files import java.nio.file.Paths import kotlin.streams.asSequence +/** + * A wrapper around JMeter to make it run without having a JMeter download installed locally. One mode is used for + * running on a remote cluster using an all-in-one bundle JAR using Capsule. The other is just used to run based on current + * classpath, but with optional SSH tunnelling logic automatically invoked. + */ class Launcher { companion object { @JvmStatic @@ -50,6 +55,8 @@ class Launcher { } private fun maybeOpenSshTunnels(args: Array): Array { + // We trim the args at the point "-Xssh" appears in the array of args. Anything after that is a host to + // SSH tunnel to. var index = 0 for (arg in args) { if (arg == "-Xssh") { From 08d3361381fd15ba9ff646f5b7193ea63c85dc7b Mon Sep 17 00:00:00 2001 From: "rick.parker" Date: Fri, 17 Nov 2017 17:38:18 +0000 Subject: [PATCH 26/37] README and fixes. --- tools/jmeter/src/main/resources/Example Flow Request.jmx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/jmeter/src/main/resources/Example Flow Request.jmx b/tools/jmeter/src/main/resources/Example Flow Request.jmx index 257573d05e..6a2d25cfcf 100644 --- a/tools/jmeter/src/main/resources/Example Flow Request.jmx +++ b/tools/jmeter/src/main/resources/Example Flow Request.jmx @@ -46,7 +46,7 @@ password - corda_is_awesome + Password Here = From 4d19a594d63c63cd184b22f94bb56869af93d9b3 Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Mon, 20 Nov 2017 17:09:59 +0000 Subject: [PATCH 27/37] Reduce the dependencies of the JMeter project by copying (#118) one function and listing required explicit dependencies instead of depending on loadtest --- tools/jmeter/build.gradle | 11 +++- .../kotlin/com/r3/corda/jmeter/JshHelper.kt | 58 +++++++++++++++++++ .../main/kotlin/com/r3/corda/jmeter/Ssh.kt | 1 - 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/JshHelper.kt diff --git a/tools/jmeter/build.gradle b/tools/jmeter/build.gradle index 8a111b9f6e..109124c69b 100644 --- a/tools/jmeter/build.gradle +++ b/tools/jmeter/build.gradle @@ -7,7 +7,16 @@ mainClassName = 'com.r3.corda.jmeter.Launcher' dependencies { compile project(':client:rpc') compile project(':finance') - compile project(':tools:loadtest') + + // https://mvnrepository.com/artifact/com.jcraft/jsch + compile group: 'com.jcraft', name: 'jsch', version: '0.1.54' + compile group: 'com.jcraft', name: 'jsch.agentproxy.core', version: '0.0.9' + compile group: 'com.jcraft', name: 'jsch.agentproxy.sshagent', version: '0.0.9' + compile group: 'com.jcraft', name: 'jsch.agentproxy.usocket-jna', version: '0.0.9' + + // Log4J: logging framework (with SLF4J bindings) + compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" + compile "org.apache.logging.log4j:log4j-core:$log4j_version" // JMeter ext.jmVersion = "3.3" diff --git a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/JshHelper.kt b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/JshHelper.kt new file mode 100644 index 0000000000..d2f7ad3477 --- /dev/null +++ b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/JshHelper.kt @@ -0,0 +1,58 @@ +package com.r3.corda.jmeter + +import com.jcraft.jsch.Buffer +import com.jcraft.jsch.Identity +import com.jcraft.jsch.IdentityRepository +import com.jcraft.jsch.JSch +import com.jcraft.jsch.agentproxy.AgentProxy +import com.jcraft.jsch.agentproxy.connector.SSHAgentConnector +import com.jcraft.jsch.agentproxy.usocket.JNAUSocketFactory +import org.slf4j.LoggerFactory +import java.util.* + +private val log = LoggerFactory.getLogger(Ssh::class.java) + +/** + * Creates a new [JSch] instance with identities loaded from the running SSH agent. + */ +fun setupJSchWithSshAgent(): JSch { + val connector = SSHAgentConnector(JNAUSocketFactory()) + val agentProxy = AgentProxy(connector) + val identities = agentProxy.identities + require(identities.isNotEmpty()) { "No SSH identities found, please add one to the agent" } + require(identities.size == 1) { "Multiple SSH identities found, don't know which one to pick" } + val identity = identities[0] + log.info("Using SSH identity ${String(identity.comment)}") + + return JSch().apply { + identityRepository = object : IdentityRepository { + override fun getStatus(): Int { + if (connector.isAvailable) { + return IdentityRepository.RUNNING + } else { + return IdentityRepository.UNAVAILABLE + } + } + + override fun getName() = connector.name + override fun getIdentities(): Vector = Vector(listOf( + object : Identity { + override fun clear() {} + override fun getAlgName() = String(Buffer(identity.blob).string) + override fun getName() = String(identity.comment) + override fun isEncrypted() = false + override fun getSignature(data: ByteArray?) = agentProxy.sign(identity.blob, data) + @Suppress("OverridingDeprecatedMember") + override fun decrypt() = true + + override fun getPublicKeyBlob() = identity.blob + override fun setPassphrase(passphrase: ByteArray?) = true + } + )) + + override fun remove(blob: ByteArray?) = throw UnsupportedOperationException() + override fun removeAll() = throw UnsupportedOperationException() + override fun add(bytes: ByteArray?) = throw UnsupportedOperationException() + } + } +} diff --git a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Ssh.kt b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Ssh.kt index 93cc88cd7c..71eb682cbd 100644 --- a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Ssh.kt +++ b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Ssh.kt @@ -3,7 +3,6 @@ package com.r3.corda.jmeter import com.jcraft.jsch.JSch import com.jcraft.jsch.Session import net.corda.core.utilities.NetworkHostAndPort -import net.corda.loadtest.setupJSchWithSshAgent import net.corda.nodeapi.internal.addShutdownHook import org.slf4j.LoggerFactory import java.io.BufferedReader From f382639ed86dceb9d21525af7be8ce93d8530420 Mon Sep 17 00:00:00 2001 From: "rick.parker" Date: Mon, 20 Nov 2017 17:52:15 +0000 Subject: [PATCH 28/37] Tidy up --- .../jmeter/{src/main/kotlin/com/r3/corda/jmeter => }/README.md | 0 tools/jmeter/build.gradle | 3 ++- 2 files changed, 2 insertions(+), 1 deletion(-) rename tools/jmeter/{src/main/kotlin/com/r3/corda/jmeter => }/README.md (100%) diff --git a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/README.md b/tools/jmeter/README.md similarity index 100% rename from tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/README.md rename to tools/jmeter/README.md diff --git a/tools/jmeter/build.gradle b/tools/jmeter/build.gradle index 109124c69b..c4c16b0241 100644 --- a/tools/jmeter/build.gradle +++ b/tools/jmeter/build.gradle @@ -47,7 +47,8 @@ task(runServer, dependsOn: 'classes', type: JavaExec) { def file = new File("$rootDir/build/search_paths.txt") file.createNewFile() file.text = "${project(':tools:jmeter').configurations.runtime.files.join(";")}" - systemProperty "search_paths_file", file.toString() systemProperty "java.rmi.server.hostname", "0.0.0.0" + systemProperty "search_paths_file", file.toString() + systemProperty "java.rmi.server.hostname", "0.0.0.0" systemProperty "jmeter.home", sourceSets.main.resources.getSrcDirs().first().getPath() // If you want to debug: jvmArgs += "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005" args+= [ "-p", sourceSets.main.resources.getSrcDirs().first().getPath()+"/jmeter.properties", From 2b217b6eea95b48fd7c99283150cba8d62c29d98 Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Thu, 23 Nov 2017 10:06:39 +0000 Subject: [PATCH 29/37] Fix ssh for windows (#121) * Make ssh tunnels work with Pageant on windows and allow specifying explicit ssh remote user * Update comments --- tools/jmeter/README.md | 19 ++++++++---- tools/jmeter/build.gradle | 12 +++++++- .../kotlin/com/r3/corda/jmeter/JshHelper.kt | 7 ++++- .../kotlin/com/r3/corda/jmeter/Launcher.kt | 25 +++++++++++----- .../main/kotlin/com/r3/corda/jmeter/Ssh.kt | 30 ++++++++++++++++--- 5 files changed, 75 insertions(+), 18 deletions(-) diff --git a/tools/jmeter/README.md b/tools/jmeter/README.md index 7166f8678f..6e67cce028 100644 --- a/tools/jmeter/README.md +++ b/tools/jmeter/README.md @@ -7,7 +7,7 @@ the Corda node via RPC. To run up the JMeter UI, using the jmeter.properties in the resources folder, type the following: -./gradlew tools:jmeter:run +`./gradlew tools:jmeter:run` You can then open the example script in "Example Flow Properties.jmx" via the File -> Open menu option. You need to configure the host, ports, user name and password in the Java Sampler that correspond to your chosen target Corda node. @@ -15,7 +15,7 @@ Simply running from the UI will result in the RPC client running inside the UI J If you wish to pass additional arguments to JMeter, you can do this: -./gradlew tools:jmeter:run -PjmeterArgs="['-n', '-Ljmeter.engine=DEBUG']" +`./gradlew tools:jmeter:run -PjmeterArgs="['-n', '-Ljmeter.engine=DEBUG']"` The intention is to run against a remote Corda node or nodes, hosted on servers rather than desktops. To this end, we leverage the JMeter ability to run remote agents that actually execute the tests, with these @@ -26,7 +26,7 @@ The remote agents then run close to the nodes, so the latency of RPC calls is mi A Capsule (http://www.capsule.io) based launchable JAR is created that can be run with the simple command line -java -jar jmeter-corda-.jar +`java -jar jmeter-corda-.jar` Embedded in the JAR is all of the corda code for flows and RPC, as well as the jmeter.propeties. This JAR will also include a properties file based on the hostname in the JMeter configuration, @@ -34,7 +34,7 @@ so we allocate different SSH tunneled port numbers this way. To launch JMeter with the tunnels automatically created: -./gradlew tools:jmeter:run -PjmeterHosts="['hostname1', 'hostname2']" +`./gradlew tools:jmeter:run -PjmeterHosts="['hostname1', 'hostname2']"` The list of hostnames should be of at least length one, with a maximum equal to the length of the remote_hosts option in jmeter.properties. We effectively "zip" together the hostnames and that list to build the SSH tunnels. @@ -45,4 +45,13 @@ from remote agents. The SSH tunnels can be started independently with: -./gradlew tools:jmeter:runSsh -PjmeterHosts="['hostname1', 'hostname2']" \ No newline at end of file +`./gradlew tools:jmeter:runSsh -PjmeterHosts="['hostname1', 'hostname2']"` + +For the ssh tunneling to work, an ssh agent must be running on your local machine with the +appropriate private key loaded. If the environment variable `SSH_AUTH_SOCK` is set, the code +assumes that a posix sshagent process is being used, if it is not set, it assumes that +[Pageant](https://www.ssh.com/ssh/putty/putty-manuals/0.68/Chapter9.html) is in use. If the +remote user name is different from the current user name, `-XsshUser ` +can be used to set this, or in the gradle call: + +`./gradlew tools:jmeter:runSsh -PjmeterHosts="['hostname1', 'hostname2']" -PsshUser="'username'"` diff --git a/tools/jmeter/build.gradle b/tools/jmeter/build.gradle index c4c16b0241..aa041346fc 100644 --- a/tools/jmeter/build.gradle +++ b/tools/jmeter/build.gradle @@ -13,6 +13,7 @@ dependencies { compile group: 'com.jcraft', name: 'jsch.agentproxy.core', version: '0.0.9' compile group: 'com.jcraft', name: 'jsch.agentproxy.sshagent', version: '0.0.9' compile group: 'com.jcraft', name: 'jsch.agentproxy.usocket-jna', version: '0.0.9' + compile group: 'com.jcraft', name: 'jsch.agentproxy.pageant', version: '0.0.9' // Log4J: logging framework (with SLF4J bindings) compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" @@ -60,8 +61,13 @@ task(runServer, dependsOn: 'classes', type: JavaExec) { task(runSsh, dependsOn: 'classes', type: JavaExec) { classpath = sourceSets.main.runtimeClasspath main = 'com.r3.corda.jmeter.Ssh' + if ( project.hasProperty("sshUser") ){ + args+= "-XsshUser" + args+= Eval.me(sshUser) + } if ( project.hasProperty("jmeterHosts") ) { - args Eval.me(jmeterHosts) + args+= "-Xssh" + args+= Eval.me(jmeterHosts) } standardInput = System.in } @@ -85,6 +91,10 @@ run { if ( project.hasProperty("jmeterArgs") ) { args+= Eval.me(jmeterHosts) } + if ( project.hasProperty("sshUser") ){ + args+= "-XsshUser" + args+= Eval.me(sshUser) + } if ( project.hasProperty("jmeterHosts") ) { args+= "-Xssh" args+= Eval.me(jmeterHosts) diff --git a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/JshHelper.kt b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/JshHelper.kt index d2f7ad3477..d92a965948 100644 --- a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/JshHelper.kt +++ b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/JshHelper.kt @@ -5,6 +5,7 @@ import com.jcraft.jsch.Identity import com.jcraft.jsch.IdentityRepository import com.jcraft.jsch.JSch import com.jcraft.jsch.agentproxy.AgentProxy +import com.jcraft.jsch.agentproxy.connector.PageantConnector import com.jcraft.jsch.agentproxy.connector.SSHAgentConnector import com.jcraft.jsch.agentproxy.usocket.JNAUSocketFactory import org.slf4j.LoggerFactory @@ -16,7 +17,11 @@ private val log = LoggerFactory.getLogger(Ssh::class.java) * Creates a new [JSch] instance with identities loaded from the running SSH agent. */ fun setupJSchWithSshAgent(): JSch { - val connector = SSHAgentConnector(JNAUSocketFactory()) + val connector = + if (System.getenv("SSH_AUTH_SOCK") == null) + PageantConnector() + else + SSHAgentConnector(JNAUSocketFactory()) val agentProxy = AgentProxy(connector) val identities = agentProxy.identities require(identities.isNotEmpty()) { "No SSH identities found, please add one to the agent" } diff --git a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Launcher.kt b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Launcher.kt index d1ebfcca23..b11d1d9f0d 100644 --- a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Launcher.kt +++ b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Launcher.kt @@ -1,5 +1,6 @@ package com.r3.corda.jmeter +import com.sun.javaws.exceptions.InvalidArgumentException import net.corda.core.internal.div import org.apache.jmeter.JMeter import org.slf4j.LoggerFactory @@ -54,19 +55,29 @@ class Launcher { } } - private fun maybeOpenSshTunnels(args: Array): Array { + fun maybeOpenSshTunnels(args: Array): Array { // We trim the args at the point "-Xssh" appears in the array of args. Anything after that is a host to - // SSH tunnel to. + // SSH tunnel to. Also get and remove the "-XsshUser" argument if it appears. var index = 0 - for (arg in args) { - if (arg == "-Xssh") { + var userName = System.getProperty("user.name") + val returnArgs = mutableListOf() + while (index < args.size) { + if (args[index] == "-XsshUser") { + ++index + if (index == args.size || args[index].startsWith("-")) { + throw InvalidArgumentException(args) + } + userName = args[index] + } else if (args[index] == "-Xssh") { // start ssh - Ssh.main(args.copyOfRange(index + 1, args.size), false) - return if (index == 0) emptyArray() else args.copyOfRange(0, index) + Ssh.createSshTunnels(args.copyOfRange(index + 1, args.size), userName, false) + return returnArgs.toTypedArray() + } else { + returnArgs.add(args[index]) } index++ } - return args + return returnArgs.toTypedArray() } } } \ No newline at end of file diff --git a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Ssh.kt b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Ssh.kt index 71eb682cbd..a01af325e5 100644 --- a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Ssh.kt +++ b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Ssh.kt @@ -2,6 +2,7 @@ package com.r3.corda.jmeter import com.jcraft.jsch.JSch import com.jcraft.jsch.Session +import com.sun.javaws.exceptions.InvalidArgumentException import net.corda.core.utilities.NetworkHostAndPort import net.corda.nodeapi.internal.addShutdownHook import org.slf4j.LoggerFactory @@ -17,9 +18,30 @@ class Ssh { val log = LoggerFactory.getLogger(this::class.java) @JvmStatic - @JvmOverloads - fun main(args: Array, wait: Boolean = true) { - val userName = System.getProperty("user.name") + fun main(args: Array) { + // parse the args and call createSshTunnels + // the only arguments recognised are "-XsshUser" for the remote user name + // and "-Xssh" - everything after this will be treated as a remote host name + var userName = System.getProperty("user.name") + var index = 0 + while (index < args.size) { + if (args[index] == "-XsshUser") { + ++index + if (index == args.size || args[index].startsWith("-")) { + throw InvalidArgumentException(args) + } + userName = args[index] + } else if (args[index] == "-Xssh") { + createSshTunnels(args.copyOfRange(index + 1, args.size), userName, true) + return + } + } + log.info("Nothing to be done - did you specify hosts to tunnel to with -Xssh?") + } + + + fun createSshTunnels(hosts: Array, userName: String, wait: Boolean) { + log.info("User name for ssh: ${userName}") val jsch = setupJSchWithSshAgent() val sessions = mutableListOf() @@ -34,7 +56,7 @@ class Ssh { // Where JMeter driver will try to connect for remote agents (should all be localhost so can ssh tunnel). val localHostsAndPorts = jmeterProps.getProperty("remote_hosts", "").split(',').map { it.trim() } - args.zip(localHostsAndPorts) { remoteHost, localHostAndPortString -> + hosts.zip(localHostsAndPorts) { remoteHost, localHostAndPortString -> // Actual remote host and port we will tunnel to. log.info("Creating tunnels for $remoteHost") val localHostAndPort = NetworkHostAndPort.parse(localHostAndPortString) From c1c42c98e4ac8041a61a48ba3c881c1994dfcd40 Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Thu, 23 Nov 2017 12:03:17 +0000 Subject: [PATCH 30/37] Make jmeter samplers depend on perftestcordapp rather than finance --- .../r3/corda/enterprise/perftestcordapp/Currencies.kt | 11 +++++++++++ tools/jmeter/build.gradle | 2 +- .../src/main/kotlin/com/r3/corda/jmeter/Samplers.kt | 4 ++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/Currencies.kt b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/Currencies.kt index 29c8d1a5ed..ceaf4e87ab 100644 --- a/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/Currencies.kt +++ b/perftestcordapp/src/main/kotlin/com/r3/corda/enterprise/perftestcordapp/Currencies.kt @@ -17,15 +17,26 @@ import java.util.* fun AMOUNT(amount: Int, token: T): Amount = Amount.fromDecimal(BigDecimal.valueOf(amount.toLong()), token) fun AMOUNT(amount: Double, token: T): Amount = Amount.fromDecimal(BigDecimal.valueOf(amount), token) +fun AMOUNT(amount: Long, token: T): Amount = Amount.fromDecimal(BigDecimal.valueOf(amount), token) fun DOLLARS(amount: Int): Amount = AMOUNT(amount, USD) fun DOLLARS(amount: Double): Amount = AMOUNT(amount, USD) +fun DOLLARS(amount: Long): Amount = AMOUNT(amount, USD) fun POUNDS(amount: Int): Amount = AMOUNT(amount, GBP) +fun POUNDS(amount: Double): Amount = AMOUNT(amount, GBP) +fun POUNDS(amount: Long): Amount = AMOUNT(amount, GBP) fun SWISS_FRANCS(amount: Int): Amount = AMOUNT(amount, CHF) +fun SWISS_FRANCS(amount: Double): Amount = AMOUNT(amount, CHF) +fun SWISS_FRANCS(amount: Long): Amount = AMOUNT(amount, CHF) val Int.DOLLARS: Amount get() = DOLLARS(this) val Double.DOLLARS: Amount get() = DOLLARS(this) +val Long.DOLLARS: Amount get() = DOLLARS(this) val Int.POUNDS: Amount get() = POUNDS(this) +val Double.POUNDS: Amount get() = POUNDS(this) +val Long.POUNDS: Amount get() = POUNDS(this) val Int.SWISS_FRANCS: Amount get() = SWISS_FRANCS(this) +val Double.SWISS_FRANCS: Amount get() = SWISS_FRANCS(this) +val Long.SWISS_FRANCS: Amount get() = SWISS_FRANCS(this) infix fun Currency.`issued by`(deposit: PartyAndReference) = issuedBy(deposit) infix fun Amount.`issued by`(deposit: PartyAndReference) = issuedBy(deposit) diff --git a/tools/jmeter/build.gradle b/tools/jmeter/build.gradle index aa041346fc..f6dbb86b94 100644 --- a/tools/jmeter/build.gradle +++ b/tools/jmeter/build.gradle @@ -6,7 +6,7 @@ mainClassName = 'com.r3.corda.jmeter.Launcher' dependencies { compile project(':client:rpc') - compile project(':finance') + compile project(':perftestcordapp') // https://mvnrepository.com/artifact/com.jcraft/jsch compile group: 'com.jcraft', name: 'jsch', version: '0.1.54' diff --git a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt index e23cc399cc..3af2e1d0f6 100644 --- a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt +++ b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt @@ -1,11 +1,11 @@ package com.r3.corda.jmeter +import com.r3.corda.enterprise.perftestcordapp.DOLLARS +import com.r3.corda.enterprise.perftestcordapp.flows.CashIssueFlow import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party import net.corda.core.messaging.CordaRPCOps import net.corda.core.utilities.OpaqueBytes -import net.corda.finance.DOLLARS -import net.corda.finance.flows.CashIssueFlow import org.apache.jmeter.config.Argument import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext From 936d25db16fa8b4dcfcdb9849f44bbc05f91d5b7 Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Thu, 23 Nov 2017 15:30:19 +0000 Subject: [PATCH 31/37] Add issue and pay sampler --- .../kotlin/com/r3/corda/jmeter/Samplers.kt | 47 ++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt index 3af2e1d0f6..9dbea5dea4 100644 --- a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt +++ b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt @@ -1,6 +1,8 @@ package com.r3.corda.jmeter import com.r3.corda.enterprise.perftestcordapp.DOLLARS +import com.r3.corda.enterprise.perftestcordapp.POUNDS +import com.r3.corda.enterprise.perftestcordapp.flows.CashIssueAndPaymentFlow import com.r3.corda.enterprise.perftestcordapp.flows.CashIssueFlow import net.corda.core.identity.CordaX500Name import net.corda.core.identity.Party @@ -19,12 +21,16 @@ abstract class AbstractSampler : BaseFlowSampler() { val notary = Argument("notaryName", "", "", "The X500 name of the notary.") } - protected fun getIdentities(rpc: CordaRPCOps, testContext: JavaSamplerContext) { - if (!testContext.containsParameter(notary.name)) { - throw IllegalStateException("You must specify the '${notary.name}' property.") + protected fun getIdentity( rpc: CordaRPCOps, testContext: JavaSamplerContext, arg: Argument):Party{ + if (!testContext.containsParameter(arg.name)) { + throw IllegalStateException("You must specify the '${arg.name}' property.") } - val notaryName = CordaX500Name.parse(testContext.getParameter(notary.name)) - notaryIdentity = rpc.wellKnownPartyFromX500Name(notaryName) ?: throw IllegalStateException("Don't know $notaryName") + val argName = CordaX500Name.parse(testContext.getParameter(arg.name)) + return rpc.wellKnownPartyFromX500Name(argName) ?: throw IllegalStateException("Don't know $argName") + } + + protected fun getNotaryIdentity(rpc: CordaRPCOps, testContext: JavaSamplerContext) { + notaryIdentity = getIdentity(rpc,testContext, notary) } } @@ -37,7 +43,7 @@ class CashIssueSampler : AbstractSampler() { override val additionalArgs: Set = setOf(notary) override fun setupTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) { - getIdentities(rpcProxy, testContext) + getNotaryIdentity(rpcProxy, testContext) } override fun teardownTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) { @@ -47,5 +53,34 @@ class CashIssueSampler : AbstractSampler() { val amount = 1_100_000_000_000.DOLLARS return FlowInvoke(CashIssueFlow::class.java, arrayOf(amount, OpaqueBytes.of(1), notaryIdentity)) } +} + +/** + * A sampler that issues cash and pays it to a specified party, thus invoking the notary and the payee + * via P2P + */ +class CashIssueAndPaySampler : AbstractSampler() { + companion object JMeterProperties{ + val otherParty = Argument("otherPartyName", "", "", "The X500 name of the payee.") + } + + lateinit var counterParty: Party + + override fun setupTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) { + getNotaryIdentity(rpcProxy,testContext) + counterParty = getIdentity(rpcProxy, testContext, otherParty) + } + + + override fun createFlowInvoke(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext): FlowInvoke<*> { + val amount = 2_000_000.POUNDS + return FlowInvoke(CashIssueAndPaymentFlow::class.java, arrayOf(amount, OpaqueBytes.of(1), notaryIdentity, counterParty)) + } + + override fun teardownTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) { + } + + override val additionalArgs: Set + get() = setOf(notary, otherParty) } \ No newline at end of file From 8240f7ba2fe94c9d0028939a4f16fe4a346600e7 Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Mon, 27 Nov 2017 11:09:51 +0000 Subject: [PATCH 32/37] Add second request type --- .../main/resources/IssueAndPay Request.jmx | 182 ++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 tools/jmeter/src/main/resources/IssueAndPay Request.jmx diff --git a/tools/jmeter/src/main/resources/IssueAndPay Request.jmx b/tools/jmeter/src/main/resources/IssueAndPay Request.jmx new file mode 100644 index 0000000000..91c842c9aa --- /dev/null +++ b/tools/jmeter/src/main/resources/IssueAndPay Request.jmx @@ -0,0 +1,182 @@ + + + + + + false + false + + + + + + + + continue + + false + 1000 + + 3 + + 1509455820000 + 1509455820000 + false + + + + + + + + + host + localhost + = + + + port + 10003 + = + + + username + corda + = + + + password + corda_is_awesome + = + + + notaryName + O=Perf-10.155.0.7, OU=Corda, L=London, C=GB + = + + + otherPartyName + L=London, C=GB, O=Perf-Networkmap, OU=Corda + = + + + + com.r3.corda.jmeter.CashIssueAndPaySampler + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + false + + saveConfig + + + true + true + true + + true + true + true + true + false + true + true + false + false + false + true + false + false + false + true + 0 + true + true + true + true + true + + + + + + + + true + + + + From 277d528849dde4a7825f596ddd139a3ab6ec06dc Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Mon, 27 Nov 2017 15:24:35 +0000 Subject: [PATCH 33/37] Fix names in jmx request --- tools/jmeter/src/main/resources/IssueAndPay Request.jmx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/jmeter/src/main/resources/IssueAndPay Request.jmx b/tools/jmeter/src/main/resources/IssueAndPay Request.jmx index 91c842c9aa..1624a808e7 100644 --- a/tools/jmeter/src/main/resources/IssueAndPay Request.jmx +++ b/tools/jmeter/src/main/resources/IssueAndPay Request.jmx @@ -51,12 +51,12 @@ notaryName - O=Perf-10.155.0.7, OU=Corda, L=London, C=GB + O=Perf-10.155.0.8, OU=Corda, L=London, C=GB = otherPartyName - L=London, C=GB, O=Perf-Networkmap, OU=Corda + O=Perf-10.155.0.7, OU=Corda, L=London, C=GB = From d9e0aa9068fffa8d3a7bb2fad08b3ecf805482da Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Mon, 27 Nov 2017 17:35:43 +0000 Subject: [PATCH 34/37] Add missing constructor parameter --- tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt index 2652817729..2c84c3bd6a 100644 --- a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt +++ b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt @@ -74,7 +74,7 @@ class CashIssueAndPaySampler : AbstractSampler() { override fun createFlowInvoke(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext): FlowInvoke<*> { val amount = 2_000_000.POUNDS - return FlowInvoke(CashIssueAndPaymentFlow::class.java, arrayOf(amount, OpaqueBytes.of(1), notaryIdentity, counterParty)) + return FlowInvoke(CashIssueAndPaymentFlow::class.java, arrayOf(amount, OpaqueBytes.of(1), notaryIdentity, true, counterParty)) } override fun teardownTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) { From c82d167296a29ba305d9ea2b97384ce150a48462 Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Mon, 27 Nov 2017 18:49:15 +0000 Subject: [PATCH 35/37] Order of parameters --- tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt index 2c84c3bd6a..aa195f7456 100644 --- a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt +++ b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt @@ -74,7 +74,7 @@ class CashIssueAndPaySampler : AbstractSampler() { override fun createFlowInvoke(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext): FlowInvoke<*> { val amount = 2_000_000.POUNDS - return FlowInvoke(CashIssueAndPaymentFlow::class.java, arrayOf(amount, OpaqueBytes.of(1), notaryIdentity, true, counterParty)) + return FlowInvoke(CashIssueAndPaymentFlow::class.java, arrayOf(amount, OpaqueBytes.of(1), counterParty, true, notaryIdentity)) } override fun teardownTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) { From f84be2a86b040f95d900942862b1b92ff846171f Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Tue, 28 Nov 2017 16:50:39 +0000 Subject: [PATCH 36/37] Put in correct notary name --- tools/jmeter/src/main/resources/Example Flow Request.jmx | 2 +- tools/jmeter/src/main/resources/IssueAndPay Request.jmx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/jmeter/src/main/resources/Example Flow Request.jmx b/tools/jmeter/src/main/resources/Example Flow Request.jmx index 6a2d25cfcf..7a6e027265 100644 --- a/tools/jmeter/src/main/resources/Example Flow Request.jmx +++ b/tools/jmeter/src/main/resources/Example Flow Request.jmx @@ -51,7 +51,7 @@ notaryName - O=Perf-10.155.0.4, OU=Corda, L=London, C=GB + O=Perf-10.155.0.8,OU=Corda,L=London,C=GB,CN=corda.node.simple = diff --git a/tools/jmeter/src/main/resources/IssueAndPay Request.jmx b/tools/jmeter/src/main/resources/IssueAndPay Request.jmx index 1624a808e7..187cb219ce 100644 --- a/tools/jmeter/src/main/resources/IssueAndPay Request.jmx +++ b/tools/jmeter/src/main/resources/IssueAndPay Request.jmx @@ -51,7 +51,7 @@ notaryName - O=Perf-10.155.0.8, OU=Corda, L=London, C=GB + O=Perf-10.155.0.8,OU=Corda,L=London,C=GB,CN=corda.node.simple = From 9e2783732ffac10d75f9100887bcfbbcdd60e149 Mon Sep 17 00:00:00 2001 From: Christian Sailer Date: Thu, 30 Nov 2017 09:23:06 +0000 Subject: [PATCH 37/37] Code review: Whitespace --- tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt index aa195f7456..3987837cab 100644 --- a/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt +++ b/tools/jmeter/src/main/kotlin/com/r3/corda/jmeter/Samplers.kt @@ -60,7 +60,7 @@ class CashIssueSampler : AbstractSampler() { * via P2P */ class CashIssueAndPaySampler : AbstractSampler() { - companion object JMeterProperties{ + companion object JMeterProperties { val otherParty = Argument("otherPartyName", "", "", "The X500 name of the payee.") }