ENT-1161 Notary load testing flow (#175)

This commit is contained in:
Rick Parker 2017-12-13 14:34:01 +00:00 committed by GitHub
parent e309095ad4
commit 8bb02c63f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 605 additions and 6 deletions

View File

@ -2,11 +2,13 @@ package net.corda.node.services.persistence
import net.corda.core.flows.StateMachineRunId
import net.corda.core.serialization.SerializedBytes
import net.corda.core.utilities.debug
import net.corda.node.services.api.CheckpointStorage
import net.corda.node.services.statemachine.Checkpoint
import net.corda.nodeapi.internal.persistence.DatabaseTransactionManager
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
import net.corda.nodeapi.internal.persistence.currentDBSession
import org.slf4j.LoggerFactory
import java.util.*
import java.util.stream.Stream
import javax.persistence.Column
@ -18,6 +20,7 @@ import javax.persistence.Lob
* Simple checkpoint key value storage in DB.
*/
class DBCheckpointStorage : CheckpointStorage {
val log = LoggerFactory.getLogger(this::class.java)
@Entity
@javax.persistence.Table(name = "${NODE_DATABASE_PREFIX}checkpoints")
@ -35,6 +38,7 @@ class DBCheckpointStorage : CheckpointStorage {
currentDBSession().saveOrUpdate(DBCheckpoint().apply {
checkpointId = id.uuid.toString()
this.checkpoint = checkpoint.bytes
log.debug { "Checkpoint $checkpointId, size=${this.checkpoint.size}" }
})
}

View File

@ -0,0 +1,9 @@
package com.r3.corda.enterprise.perftestcordapp
import net.corda.core.serialization.SerializationWhitelist
import java.util.*
class Whitelist : SerializationWhitelist {
override val whitelist: List<Class<*>> = listOf(LinkedList::class.java)
}

View File

@ -0,0 +1,36 @@
package com.r3.corda.enterprise.perftestcordapp.contracts
import net.corda.core.contracts.*
import net.corda.core.identity.AbstractParty
import net.corda.core.transactions.LedgerTransaction
import java.time.Instant
/**
* A lightweight `LinearState` based contract and state for use with notary performance testing.
*
* The verify method is mostly empty. All it expects is a single command. No additional vault schemas are defined.
*/
class LinearStateBatchNotariseContract : Contract {
companion object {
const val CP_PROGRAM_ID: ContractClassName = "com.r3.corda.enterprise.perftestcordapp.contracts.LinearStateBatchNotariseContract"
}
data class State(
override val linearId: UniqueIdentifier,
val creator: AbstractParty,
val creationStamp: Instant
) : LinearState {
override val participants = listOf(creator)
}
interface Commands : CommandData {
class Create : TypeOnlyCommandData(), Commands
class Evolve : TypeOnlyCommandData(), Commands
}
override fun verify(tx: LedgerTransaction) {
val command = tx.commands.requireSingleCommand<Commands>()
val timeWindow: TimeWindow? = tx.timeWindow
}
}

View File

@ -0,0 +1,131 @@
package com.r3.corda.enterprise.perftestcordapp.flows
import co.paralleluniverse.fibers.Suspendable
import com.r3.corda.enterprise.perftestcordapp.contracts.LinearStateBatchNotariseContract
import net.corda.core.contracts.TimeWindow
import net.corda.core.contracts.TransactionState
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.internal.times
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker
import java.time.Duration
import java.time.Instant
import java.util.*
import java.util.concurrent.TimeUnit
/**
* A flow that generates N linear states, and then evolves them X times, as close to the specified rate as possible.
*
* @property notary The notary to use for notarising the evolution transactions (not the initial, which is unnotarised).
* @property n The number of states per transaction.
* @property x The number of iterations to do (so overall, we generate x+1 transactions).
* @property logIterations If true, will log at info level the iteration the flow is on (helpful if trying to see progress in node logs).
* @property transactionsPerSecond A target number of transactions to generate per second. The target may not be achieved.
*/
@StartableByRPC
class LinearStateBatchNotariseFlow(private val notary: Party,
private val n: Int,
private val x: Int,
private val logIterations: Boolean,
private val transactionsPerSecond: Double
) : FlowLogic<LinearStateBatchNotariseFlow.Result>() {
companion object {
object GENERATING_INITIAL_TX : ProgressTracker.Step("Generating initial transaction")
object EVOLVING_STATES_TX : ProgressTracker.Step("Generating transaction to evolve states")
object SENDING_RESULTS : ProgressTracker.Step("Sending results")
fun tracker() = ProgressTracker(GENERATING_INITIAL_TX, EVOLVING_STATES_TX, SENDING_RESULTS)
}
override val progressTracker: ProgressTracker = tracker()
@Suspendable
override fun call(): Result {
progressTracker.currentStep = GENERATING_INITIAL_TX
val us = serviceHub.myInfo.legalIdentities.first()
var inputTx = buildInitialTx(us)
progressTracker.currentStep = EVOLVING_STATES_TX
val durationOfEachIteration = Duration.ofHours(1).dividedBy((transactionsPerSecond * TimeUnit.SECONDS.convert(1, TimeUnit.HOURS)).toLong())
val measurements = LinkedList<Measurement>()
val iterationStartTime = serviceHub.clock.instant()
(0 until x).forEach { iterationNumber ->
val expectedTimeOfNextIteration = iterationStartTime.plus(durationOfEachIteration.times(iterationNumber.toLong()))
val sleepDuration = Duration.between(serviceHub.clock.instant(), expectedTimeOfNextIteration)
if (!sleepDuration.isNegative && !sleepDuration.isZero) {
sleep(sleepDuration)
}
if (logIterations) {
logger.info("ITERATION ${iterationNumber + 1} of $x, with $n states. Slept for $sleepDuration")
}
buildEvolveTx(inputTx, us, iterationNumber, sleepDuration).apply {
inputTx = first
measurements += second
}
}
progressTracker.currentStep = SENDING_RESULTS
return Result(measurements)
}
@Suspendable
private fun buildEvolveTx(inputTx: SignedTransaction, us: Party, iterationNumber: Int, sleepDuration: Duration): Pair<SignedTransaction, Measurement> {
val tx = assembleEvolveTx(inputTx, us)
val startTime = serviceHub.clock.instant()
val stx = finaliseTx(tx, "Unable to notarise initial evolution transaction, iteration $iterationNumber.")
val endTime = serviceHub.clock.instant()
return stx to Measurement(startTime, endTime, sleepDuration)
}
@Suspendable
private fun assembleEvolveTx(inputTx: SignedTransaction, us: Party): SignedTransaction {
val wtx = inputTx.tx
val builder = TransactionBuilder(notary)
(0 until n).forEach { outputIndex ->
val input = wtx.outRef<LinearStateBatchNotariseContract.State>(outputIndex)
builder.addInputState(input)
builder.addOutputState(TransactionState(LinearStateBatchNotariseContract.State(input.state.data.linearId, us, serviceHub.clock.instant()), LinearStateBatchNotariseContract.CP_PROGRAM_ID, notary))
}
builder.addCommand(LinearStateBatchNotariseContract.Commands.Evolve(), us.owningKey)
builder.setTimeWindow(TimeWindow.fromOnly(serviceHub.clock.instant()))
val tx = serviceHub.signInitialTransaction(builder, us.owningKey)
return tx
}
@Suspendable
private fun buildInitialTx(us: Party): SignedTransaction {
val tx = assembleInitialTx(us)
return finaliseTx(tx, "Unable to notarise initial generation transaction.")
}
@Suspendable
private fun assembleInitialTx(us: Party): SignedTransaction {
val builder = TransactionBuilder(notary)
(0 until n).forEach { outputIndex ->
builder.addOutputState(TransactionState(LinearStateBatchNotariseContract.State(UniqueIdentifier(), us, serviceHub.clock.instant()), LinearStateBatchNotariseContract.CP_PROGRAM_ID, notary))
}
builder.addCommand(LinearStateBatchNotariseContract.Commands.Create(), us.owningKey)
builder.setTimeWindow(TimeWindow.fromOnly(serviceHub.clock.instant()))
val tx = serviceHub.signInitialTransaction(builder, us.owningKey)
return tx
}
@Suspendable
protected fun finaliseTx(tx: SignedTransaction, message: String): SignedTransaction {
try {
return subFlow(FinalityFlow(tx))
} catch (e: NotaryException) {
throw FlowException(message, e)
}
}
@CordaSerializable
data class Result(val measurements: LinkedList<Measurement>)
@CordaSerializable
data class Measurement(val start: Instant, val end: Instant, val delay: Duration)
}

View File

@ -0,0 +1,2 @@
com.r3.corda.enterprise.perftestcordapp.Whitelist

View File

@ -48,11 +48,15 @@ abstract class BaseFlowSampler() : AbstractJavaSamplerClient() {
setupTest(rpcProxy!!, context)
}
protected open fun additionalFlowResponseProcessing(context: JavaSamplerContext, sample: SampleResult, response: Any?) {
// Override this if you want to contribute things from the flow result to the sample.
}
override fun runTest(context: JavaSamplerContext): SampleResult {
val flowInvoke = createFlowInvoke(rpcProxy!!, context)
val result = SampleResult()
result.sampleStart()
val handle = rpcProxy!!.startFlowDynamic(flowInvoke!!.flowLogicClass, *(flowInvoke!!.args))
val handle = rpcProxy!!.startFlowDynamic(flowInvoke.flowLogicClass, *(flowInvoke.args))
result.sampleLabel = handle.id.toString()
result.latencyEnd()
try {
@ -60,11 +64,14 @@ abstract class BaseFlowSampler() : AbstractJavaSamplerClient() {
result.sampleEnd()
return result.apply {
isSuccessful = true
additionalFlowResponseProcessing(context, this, flowResult)
}
} catch (e: Exception) {
result.sampleEnd()
e.printStackTrace()
return result.apply {
isSuccessful = false
additionalFlowResponseProcessing(context, this, e)
}
}
}

View File

@ -5,12 +5,15 @@ import com.r3.corda.enterprise.perftestcordapp.POUNDS
import com.r3.corda.enterprise.perftestcordapp.flows.CashIssueAndPaymentFlow
import com.r3.corda.enterprise.perftestcordapp.flows.CashIssueAndPaymentNoSelection
import com.r3.corda.enterprise.perftestcordapp.flows.CashIssueFlow
import com.r3.corda.enterprise.perftestcordapp.flows.LinearStateBatchNotariseFlow
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 org.apache.jmeter.config.Argument
import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext
import org.apache.jmeter.samplers.SampleResult
import java.util.*
/**
* A base sampler that looks up identities via RPC ready for starting flows, to be extended and specialised as required.
@ -90,4 +93,116 @@ class CashIssueAndPaySampler : AbstractSampler() {
override val additionalArgs: Set<Argument>
get() = setOf(notary, otherParty, coinSelection)
}
/**
* A sampler that attempts to generate load on the Notary.
*
* It builds a transaction of multiple `LinearState`s and then for each iteration transitions each state as a batch in
* a single transaction and sends it to be notarised. That way the size of the transaction, both in bytes and number of
* states, can be varied (not independently currently).
*
* The requesting flow does this for a specified number of iterations, and returns a result that is then fed back to JMeter.
* So don't be surprised if it looks like JMeter is getting no results for a while. It only receives them when the flow
* finishes.
* If JMeter asks for more samples/iterations than that, then another flow is kicked off automatically if the repeat property
* is set to true, otherwise it will return an error/failure for the next sample.
*
* The flow will throttle (if necessary) to maintain a specified number of iterations/transactions per second. This is
* aggregated over all iterations, so GC pauses etc shouldn't reduce the overall number of transactions in a time period,
* unless the node / notary is unable to keep up. If falling behind, the flow will not pause between transactions.
*/
class LinearStateBatchNotariseSampler : AbstractSampler() {
companion object JMeterProperties {
val numberOfStates = Argument("numStates", "1", "<meta>", "Number of linear states to include in each transaction.")
val numberOfIterations = Argument("numIterations", "1", "<meta>", "Number of iterations / evolutions to do. Each iteration generates one transaction.")
val logIterations = Argument("enableLog", "false", "<meta>", "Print in the logs what iteration the test is on etc.")
val numberOfTps = Argument("transactionsPerSecond", "1.0", "<meta>", "Transaction per second target.")
val repeat = Argument("repeatInvoke", "false", "<meta>", "If true, invoke the flow again if JMeter expects more iterations.")
}
var n: Int = 0
var x: Int = 0
var log: Boolean = false
var tps: Double = 1.0
var reRequest: Boolean = false
var measurements: LinkedList<LinkedList<LinearStateBatchNotariseFlow.Measurement>> = LinkedList()
var measurementsSize: Int = 0
var nextIteration: Int = 0
var sample: SampleResult? = null
override val additionalArgs: Set<Argument> = setOf(notary, numberOfStates, numberOfIterations, logIterations, numberOfTps, repeat)
// At test setup, we fire off one big request via RPC.
override fun setupTest(context: JavaSamplerContext) {
measurements.clear()
super.setupTest(context)
println("Running test $context")
super.runTest(context)
}
override fun setupTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) {
getNotaryIdentity(rpcProxy, testContext)
n = testContext.getParameter(numberOfStates.name, numberOfStates.value).toInt()
x = testContext.getParameter(numberOfIterations.name, numberOfIterations.value).toInt()
log = testContext.getParameter(logIterations.name, logIterations.value).toBoolean()
tps = testContext.getParameter(numberOfTps.name, numberOfTps.value).toDouble()
reRequest = testContext.getParameter(repeat.name, repeat.value).toBoolean()
}
override fun teardownTest(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext) {
}
override fun createFlowInvoke(rpcProxy: CordaRPCOps, testContext: JavaSamplerContext): FlowInvoke<LinearStateBatchNotariseFlow> {
return FlowInvoke<LinearStateBatchNotariseFlow>(LinearStateBatchNotariseFlow::class.java, arrayOf(notaryIdentity, n, x, log, tps))
}
override fun additionalFlowResponseProcessing(context: JavaSamplerContext, sample: SampleResult, response: Any?) {
if (response is LinearStateBatchNotariseFlow.Result && response.measurements.isNotEmpty()) {
measurements.add(response.measurements)
measurementsSize += response.measurements.size
}
this.sample = sample
}
private fun nextMeasurement(context: JavaSamplerContext): LinearStateBatchNotariseFlow.Measurement {
val firstList = measurements.first()
val measurement = firstList.remove()
measurementsSize--
if (firstList.isEmpty()) {
measurements.remove()
nextIteration = 0
// if a flag is set, run the flow again
if (reRequest) {
println("Re-running test $context")
super.runTest(context)
}
}
return measurement
}
// Each iteration of the test returns the next measurement from the large batch request.
override fun runTest(context: JavaSamplerContext): SampleResult {
val topLevelSample = sample ?: SampleResult().apply { isSuccessful = false }
val currentIteration = nextIteration++
// Build samples based on the response.
val result = if (topLevelSample.isSuccessful && measurementsSize > 0) {
val measurement = nextMeasurement(context)
val result = SampleResult(measurement.end.toEpochMilli(), measurement.end.toEpochMilli() - measurement.start.toEpochMilli())
val delay = measurement.delay.toMillis()
if (delay < 0) {
result.latency = -delay
}
result.isSuccessful = true
result.sampleLabel = "${topLevelSample.sampleLabel}-$currentIteration"
result
} else {
val result = SampleResult(topLevelSample.timeStamp, 0)
result.isSuccessful = false
result.sampleLabel = if (!topLevelSample.isSuccessful) "${topLevelSample.sampleLabel}-$currentIteration" else "${topLevelSample.sampleLabel}-END"
result
}
return result
}
}

View File

@ -0,0 +1,291 @@
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="3.2" jmeter="3.3 r1808647">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
<stringProp name="TestPlan.comments"></stringProp>
<boolProp name="TestPlan.functional_mode">false</boolProp>
<boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="TestPlan.user_define_classpath"></stringProp>
</TestPlan>
<hashTree>
<ResultCollector guiclass="TableVisualizer" testclass="ResultCollector" testname="View Results in Table" enabled="true">
<boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
<name>saveConfig</name>
<value class="SampleSaveConfiguration">
<time>true</time>
<latency>true</latency>
<timestamp>true</timestamp>
<success>true</success>
<label>true</label>
<code>true</code>
<message>true</message>
<threadName>true</threadName>
<dataType>true</dataType>
<encoding>false</encoding>
<assertions>true</assertions>
<subresults>true</subresults>
<responseData>false</responseData>
<samplerData>false</samplerData>
<xml>false</xml>
<fieldNames>true</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
<sentBytes>true</sentBytes>
<threadCounts>true</threadCounts>
<idleTime>true</idleTime>
<connectTime>true</connectTime>
</value>
</objProp>
<stringProp name="filename"></stringProp>
<boolProp name="ResultCollector.success_only_logging">true</boolProp>
</ResultCollector>
<hashTree/>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true">
<stringProp name="ThreadGroup.on_sample_error">stopthread</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
<boolProp name="LoopController.continue_forever">false</boolProp>
<intProp name="LoopController.loops">-1</intProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">3</stringProp>
<stringProp name="ThreadGroup.ramp_time"></stringProp>
<longProp name="ThreadGroup.start_time">1509455820000</longProp>
<longProp name="ThreadGroup.end_time">1509455820000</longProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.duration"></stringProp>
<stringProp name="ThreadGroup.delay"></stringProp>
</ThreadGroup>
<hashTree>
<JavaSampler guiclass="JavaTestSamplerGui" testclass="JavaSampler" testname="Linear State Batch Notarise Request" enabled="true">
<elementProp name="arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="host" elementType="Argument">
<stringProp name="Argument.name">host</stringProp>
<stringProp name="Argument.value">localhost</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="port" elementType="Argument">
<stringProp name="Argument.name">port</stringProp>
<stringProp name="Argument.value">10004</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="username" elementType="Argument">
<stringProp name="Argument.name">username</stringProp>
<stringProp name="Argument.value">perf</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="password" elementType="Argument">
<stringProp name="Argument.name">password</stringProp>
<stringProp name="Argument.value">perf</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="notaryName" elementType="Argument">
<stringProp name="Argument.name">notaryName</stringProp>
<stringProp name="Argument.value">O=Notary Service,L=Zurich,C=CH,CN=corda.notary.simple</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="numStates" elementType="Argument">
<stringProp name="Argument.name">numStates</stringProp>
<stringProp name="Argument.value">100</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="numIterations" elementType="Argument">
<stringProp name="Argument.name">numIterations</stringProp>
<stringProp name="Argument.value">1</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="enableLog" elementType="Argument">
<stringProp name="Argument.name">enableLog</stringProp>
<stringProp name="Argument.value">true</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="transactionsPerMinute" elementType="Argument">
<stringProp name="Argument.name">transactionsPerMinute</stringProp>
<stringProp name="Argument.value">10</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="repeatInvoke" elementType="Argument">
<stringProp name="Argument.name">repeatInvoke</stringProp>
<stringProp name="Argument.value">true</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="classname">com.r3.corda.jmeter.LinearStateBatchNotariseSampler</stringProp>
</JavaSampler>
<hashTree/>
<JavaSampler guiclass="JavaTestSamplerGui" testclass="JavaSampler" testname="Cash Issue Request" enabled="false">
<elementProp name="arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="host" elementType="Argument">
<stringProp name="Argument.name">host</stringProp>
<stringProp name="Argument.value">localhost</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="port" elementType="Argument">
<stringProp name="Argument.name">port</stringProp>
<stringProp name="Argument.value">10004</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="username" elementType="Argument">
<stringProp name="Argument.name">username</stringProp>
<stringProp name="Argument.value">perf</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="password" elementType="Argument">
<stringProp name="Argument.name">password</stringProp>
<stringProp name="Argument.value">perf</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="notaryName" elementType="Argument">
<stringProp name="Argument.name">notaryName</stringProp>
<stringProp name="Argument.value">O=Notary Service,L=Zurich,C=CH,CN=corda.notary.simple</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="otherPartyName" elementType="Argument">
<stringProp name="Argument.name">otherPartyName</stringProp>
<stringProp name="Argument.value">O=Bank B,L=New York,C=US</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="useCoinSelection" elementType="Argument">
<stringProp name="Argument.name">useCoinSelection</stringProp>
<stringProp name="Argument.value">true</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="classname">com.r3.corda.jmeter.CashIssueAndPaySampler</stringProp>
</JavaSampler>
<hashTree/>
</hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true">
<stringProp name="ThreadGroup.on_sample_error">stopthread</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
<boolProp name="LoopController.continue_forever">false</boolProp>
<intProp name="LoopController.loops">-1</intProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">3</stringProp>
<stringProp name="ThreadGroup.ramp_time"></stringProp>
<longProp name="ThreadGroup.start_time">1509455820000</longProp>
<longProp name="ThreadGroup.end_time">1509455820000</longProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.duration"></stringProp>
<stringProp name="ThreadGroup.delay"></stringProp>
</ThreadGroup>
<hashTree>
<JavaSampler guiclass="JavaTestSamplerGui" testclass="JavaSampler" testname="Linear State Batch Notarise Request" enabled="true">
<elementProp name="arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="host" elementType="Argument">
<stringProp name="Argument.name">host</stringProp>
<stringProp name="Argument.value">localhost</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="port" elementType="Argument">
<stringProp name="Argument.name">port</stringProp>
<stringProp name="Argument.value">10007</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="username" elementType="Argument">
<stringProp name="Argument.name">username</stringProp>
<stringProp name="Argument.value">perf</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="password" elementType="Argument">
<stringProp name="Argument.name">password</stringProp>
<stringProp name="Argument.value">perf</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="notaryName" elementType="Argument">
<stringProp name="Argument.name">notaryName</stringProp>
<stringProp name="Argument.value">O=Notary Service,L=Zurich,C=CH,CN=corda.notary.simple</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="numStates" elementType="Argument">
<stringProp name="Argument.name">numStates</stringProp>
<stringProp name="Argument.value">100</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="numIterations" elementType="Argument">
<stringProp name="Argument.name">numIterations</stringProp>
<stringProp name="Argument.value">1</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="enableLog" elementType="Argument">
<stringProp name="Argument.name">enableLog</stringProp>
<stringProp name="Argument.value">true</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="transactionsPerMinute" elementType="Argument">
<stringProp name="Argument.name">transactionsPerMinute</stringProp>
<stringProp name="Argument.value">10</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="repeatInvoke" elementType="Argument">
<stringProp name="Argument.name">repeatInvoke</stringProp>
<stringProp name="Argument.value">true</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="classname">com.r3.corda.jmeter.LinearStateBatchNotariseSampler</stringProp>
</JavaSampler>
<hashTree/>
<JavaSampler guiclass="JavaTestSamplerGui" testclass="JavaSampler" testname="Cash Issue Request" enabled="false">
<elementProp name="arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="host" elementType="Argument">
<stringProp name="Argument.name">host</stringProp>
<stringProp name="Argument.value">localhost</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="port" elementType="Argument">
<stringProp name="Argument.name">port</stringProp>
<stringProp name="Argument.value">10004</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="username" elementType="Argument">
<stringProp name="Argument.name">username</stringProp>
<stringProp name="Argument.value">perf</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="password" elementType="Argument">
<stringProp name="Argument.name">password</stringProp>
<stringProp name="Argument.value">perf</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="notaryName" elementType="Argument">
<stringProp name="Argument.name">notaryName</stringProp>
<stringProp name="Argument.value">O=Notary Service,L=Zurich,C=CH,CN=corda.notary.simple</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="otherPartyName" elementType="Argument">
<stringProp name="Argument.name">otherPartyName</stringProp>
<stringProp name="Argument.value">O=Bank B,L=New York,C=US</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="useCoinSelection" elementType="Argument">
<stringProp name="Argument.name">useCoinSelection</stringProp>
<stringProp name="Argument.value">true</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="classname">com.r3.corda.jmeter.CashIssueAndPaySampler</stringProp>
</JavaSampler>
<hashTree/>
</hashTree>
</hashTree>
<WorkBench guiclass="WorkBenchGui" testclass="WorkBench" testname="WorkBench" enabled="true">
<boolProp name="WorkBench.save">true</boolProp>
</WorkBench>
<hashTree/>
</hashTree>
</jmeterTestPlan>

View File

@ -5,29 +5,33 @@ import net.corda.node.services.Permissions
import net.corda.nodeapi.internal.config.User
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.node.NotarySpec
import org.slf4j.LoggerFactory
import java.io.BufferedReader
import java.io.InputStreamReader
class StartLocalPerfCorDapp {
companion object {
val log = LoggerFactory.getLogger(this::class.java)
@JvmStatic
fun main(args: Array<String>) {
// Typically the RPC port of Bank A is 10004.
val demoUser = User("perf", "perf", setOf(Permissions.all()))
net.corda.testing.driver.driver(startNodesInProcess = false,
waitForAllNodesToFinish = true,
//isDebug = true,
notarySpecs = listOf(NotarySpec(DUMMY_NOTARY.name, validating = false)),
extraCordappPackagesToScan = listOf("com.r3.corda.enterprise.perftestcordapp")) {
val (nodeA, nodeB) = listOf(
startNode(providedName = net.corda.testing.DUMMY_BANK_A.name, rpcUsers = listOf(demoUser)),
startNode(providedName = net.corda.testing.DUMMY_BANK_B.name, rpcUsers = listOf(demoUser))
startNode(providedName = net.corda.testing.DUMMY_BANK_A.name, rpcUsers = listOf(demoUser), maximumHeapSize = "1G"),
startNode(providedName = net.corda.testing.DUMMY_BANK_B.name, rpcUsers = listOf(demoUser), maximumHeapSize = "1G")
).map { it.getOrThrow() }
println("Nodes started!")
log.info("Nodes started!")
val input = BufferedReader(InputStreamReader(System.`in`))
do {
Ssh.log.info("Type 'quit' to exit cleanly.")
log.info("Type 'quit' to exit cleanly.")
} while (input.readLine() != "quit")
println("Quitting... (this sometimes takes a while)")
log.info("Quitting... (this sometimes takes a while)")
nodeA.stop()
nodeB.stop()
defaultNotaryHandle.nodeHandles.getOrThrow().map { it.stop() }