JMS Performance Test Tool Evaluation
JMS Performance Test Tool Evaluation
JMS Performance Test Tool Evaluation
An experiment
4/27/2011 Page 2 of 30
Introduction..........................................................................................................................3
Environment.........................................................................................................................3
Objective..............................................................................................................................3
Approach..............................................................................................................................4
.............................................................................................................................................4
Observations........................................................................................................................4
3. Tool Evaluation................................................................................................................5
References............................................................................................................................6
Appendix..............................................................................................................................8
4/27/2011 Page 3 of 30
Introduction
Performance testing primarily involves 3 tasks.
1. Drive the load
2. Monitor the metrics when the load is hitting the server.
3. Analyze the results and present the observations.
A small experiment has been done to see which tool would be good to be used for testing
a JMS Product. This document should not be treated as a comprehensive analysis of all
the features the tools support but will only summarize the efforts made, to make a simple
set of JMS features working through each of the tools under test. JMeter, Grinder,
LoadRunner are the tools that have been considered for the experiment. Below is
presented a summary of the efforts made for that purpose.
Environment
As a first step, Java MQ server has been installed.
All the performance testing tools required an LDAP server to be setup to run the tests
They looked for the LDAP server to build the InitialContext. It is for that purpose
OpenDS has been chosen for doing the evaluation.
JConsole has been used to monitor the broker. The support provided in the testing tool
can also be used to measure metrics like response times and CPU utilization. However it
is to note that response times will include latencies that are brought in by the network
connectivity and the number of hops.
Objective
Develop a test case to send/receive to/from a queue.
Develop a test case to publish/subscribe to/from a topic.
And
• Configurable Tool
• Multithreading Support (Create Multiple Pub/Sub Queue/Topic)
• Data generating mechanism
• Ease of Use
4/27/2011 Page 4 of 30
Approach
Imqadmin, the default tool provided by Sun JMS has been used and the following objects
have been added.
java.naming.provider.url ldap://localhost:389
java.naming.factory.initial com.sun.jndi.ldap.LdapCtxFactory
java.naming.security.principal cn=Directory Manager
java.naming.security.credentials admin
java.naming.security.authentication simple
After that, sample clients programs have been written using each of the testing tools to
drive load to hit the JMS server.
Observations
1. Each tool provides support for performance testing a JMS product in a different
way.
JMeter has a UI based JMS configuration tool for Queues and Topics separately.
No coding is required for doing the basic tasks.
They may be a necessity for tweaking the tool a bit for achieving what is desired for test.
Grinder
LoadRunner
A java virtual user protocol has to be used to write the test client.
4/27/2011 Page 5 of 30
Though this test harness is being told as generic for any JMS product, it requires more
effort than expected in terms of setting up the properties and changing stuff related to the
Sun product. On top of that it does not provide very good analysis features and lacks
features that a performance test tool would have. It can be considered as a framework
rather than a tool and hence it has been removed out of the scope for analysis of the tools.
JMeter
The message fields cannot be set without tweaking the java files and recompiling JMeter.
Grinder
LoadRunner
Found issues with the topic examples. They compile fine but do not hit the server. Tool
reports that messages have been sent but JConsole does not show that the messages are
consumed by the broker.
3. Tool Evaluation
Points 1-5 are given against each of the attributes
Configurable 2 3 3 2
Tool
Multithreading 3 2 3 2
Support ( Create
4/27/2011 Page 6 of 30
Multiple Pub/Sub
Queue/Topic)
Data Generating 2 2 3 2
mechanism
Ease of Use 4 2 3 1
Java/JMS 4 3 2 3
version support
Reporting 1 2 4 1
Points 16 14 18 11
References
Sun MQ Server
https://mq.dev.java.net
OpenDS
https://opends.dev.java.net/
JConsole
http://java.sun.com/developer/technicalArticles/J2SE/jconsole.html
JMeter
http://jakarta.apache.org/jmeter/
JMeter Framework
http://jakarta.apache.org/jmeter/usermanual/build-jms-topic-test-plan.html
http://jakarta.apache.org/jmeter/usermanual/build-jms-point-to-point-test-plan.html
Grinder
http://grinder.sourceforge.net
HP Loadrunner
https://h10078.www1.hp.com/cda/hpms/display/main/hpms_content.jsp?zn=bto&cp=1-
11-126-17^8_4000_100__
Appendix
JMeter
<stringProp
name="JMSSampler.SendQueue">cn=Q1;dc=example;dc=com</stringProp>
<stringProp name="JMSSampler.ReceiveQueue"></stringProp>
<boolProp name="JMSSampler.isFireAndForget">true</boolProp>
<boolProp name="JMSSampler.isNonPersistent">false</boolProp>
<stringProp name="JMSSampler.timeout">2000</stringProp>
<stringProp name="HTTPSamper.xml_data">test</stringProp>
<stringProp name="JMSSampler.initialContextFactory"></stringProp>
<stringProp name="JMSSampler.contextProviderUrl"></stringProp>
<elementProp name="JMSSampler.jndiProperties" elementType="Arguments"
guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables"
enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="java.naming.provider.url" elementType="Argument">
<stringProp name="Argument.name">java.naming.provider.url</stringProp>
<stringProp name="Argument.value">ldap://localhost:389</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="java.naming.factory.initial" elementType="Argument">
<stringProp name="Argument.name">java.naming.factory.initial</stringProp>
<stringProp
name="Argument.value">com.sun.jndi.ldap.LdapCtxFactory</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="java.naming.security.principal"
elementType="Argument">
<stringProp
name="Argument.name">java.naming.security.principal</stringProp>
<stringProp name="Argument.value">cn=Directory Manager</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="java.naming.security.credentials"
elementType="Argument">
<stringProp
name="Argument.name">java.naming.security.credentials</stringProp>
<stringProp name="Argument.value">admin</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<elementProp name="arguments" elementType="Arguments"
guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables"
enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
</JMSSampler>
4/27/2011 Page 10 of 30
<hashTree/>
<ResultCollector guiclass="GraphVisualizer" testclass="ResultCollector"
testname="Graph Results" 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>true</xml>
<fieldNames>false</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMess
age>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
</hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup"
testname="Sender-Receiver" enabled="true">
<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">1</stringProp>
<stringProp name="ThreadGroup.ramp_time">1</stringProp>
4/27/2011 Page 11 of 30
<longProp name="ThreadGroup.start_time">1218728091000</longProp>
<longProp name="ThreadGroup.end_time">1218728091000</longProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<stringProp name="ThreadGroup.duration"></stringProp>
<stringProp name="ThreadGroup.delay"></stringProp>
</ThreadGroup>
<hashTree>
<JMSSampler guiclass="JMSConfigGui" testclass="JMSSampler"
testname="LDAP" enabled="true">
<stringProp
name="JMSSampler.queueconnectionfactory">cn=QCF;dc=example;dc=com</stringPro
p>
<stringProp
name="JMSSampler.SendQueue">cn=Q1;dc=example;dc=com</stringProp>
<stringProp
name="JMSSampler.ReceiveQueue">cn=Q1;dc=example;dc=com</stringProp>
<boolProp name="JMSSampler.isFireAndForget">false</boolProp>
<boolProp name="JMSSampler.isNonPersistent">false</boolProp>
<stringProp name="JMSSampler.timeout">2000</stringProp>
<stringProp name="HTTPSamper.xml_data">test</stringProp>
<stringProp name="JMSSampler.initialContextFactory"></stringProp>
<stringProp name="JMSSampler.contextProviderUrl"></stringProp>
<elementProp name="JMSSampler.jndiProperties" elementType="Arguments"
guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables"
enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="java.naming.provider.url" elementType="Argument">
<stringProp name="Argument.name">java.naming.provider.url</stringProp>
<stringProp name="Argument.value">ldap://localhost:389</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="java.naming.factory.initial" elementType="Argument">
<stringProp name="Argument.name">java.naming.factory.initial</stringProp>
<stringProp
name="Argument.value">com.sun.jndi.ldap.LdapCtxFactory</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="java.naming.security.principal"
elementType="Argument">
<stringProp
name="Argument.name">java.naming.security.principal</stringProp>
<stringProp name="Argument.value">cn=Directory Manager</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
4/27/2011 Page 12 of 30
<elementProp name="java.naming.security.credentials"
elementType="Argument">
<stringProp
name="Argument.name">java.naming.security.credentials</stringProp>
<stringProp name="Argument.value">admin</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<elementProp name="arguments" elementType="Arguments"
guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables"
enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
</JMSSampler>
<hashTree>
<ResultCollector guiclass="GraphVisualizer" testclass="ResultCollector"
testname="Graph Results" 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>true</xml>
<fieldNames>false</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
<saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMes
sage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
</value>
4/27/2011 Page 13 of 30
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
</hashTree>
</hashTree>
</hashTree>
</hashTree>
</jmeterTestPlan>
<hashTree>
<PublisherSampler guiclass="JMSPublisherGui" testclass="PublisherSampler"
testname="LDAP" enabled="true">
<stringProp name="jms.jndi_properties">false</stringProp>
<stringProp
name="jms.initial_context_factory">com.sun.jndi.ldap.LdapCtxFactory</stringProp>
<stringProp name="jms.provider_url">ldap://localhost:389</stringProp>
<stringProp
name="jms.connection_factory">cn=TCF;dc=example;dc=com</stringProp>
<stringProp name="jms.topic">cn=T1;dc=example;dc=com</stringProp>
<stringProp name="jms.security_principle">cn=Directory Manager</stringProp>
<stringProp name="jms.security_credentials">admin</stringProp>
<stringProp name="jms.text_message">test</stringProp>
<stringProp name="jms.input_file"></stringProp>
<stringProp name="jms.random_path"></stringProp>
<stringProp name="jms.config_choice">Textarea</stringProp>
<stringProp name="jms.config_msg_type">Text Message</stringProp>
<stringProp name="jms.iterations">10</stringProp>
<stringProp name="jms.authenticate">Required</stringProp>
</PublisherSampler>
<hashTree>
<ResultCollector guiclass="GraphVisualizer" testclass="ResultCollector"
testname="Graph Results" 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>true</xml>
<fieldNames>false</fieldNames>
<responseHeaders>false</responseHeaders>
<requestHeaders>false</requestHeaders>
<responseDataOnError>false</responseDataOnError>
4/27/2011 Page 15 of 30
<saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMes
sage>
<assertionsResultsToSave>0</assertionsResultsToSave>
<bytes>true</bytes>
</value>
</objProp>
<stringProp name="filename"></stringProp>
</ResultCollector>
<hashTree/>
</hashTree>
</hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup"
testname="Subscriber" enabled="true">
<elementProp name="ThreadGroup.main_controller"
elementType="LoopController" guiclass="LoopControlPanel"
testclass="LoopController" testname="Loop Controller" enabled="true">
<boolProp name="LoopController.continue_forever">false</boolProp>
<stringProp name="LoopController.loops">10</stringProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">1</stringProp>
<stringProp name="ThreadGroup.ramp_time">0</stringProp>
<longProp name="ThreadGroup.start_time">1218729154000</longProp>
<longProp name="ThreadGroup.end_time">1218729154000</longProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.on_sample_error">stopthread</stringProp>
<stringProp name="ThreadGroup.duration"></stringProp>
<stringProp name="ThreadGroup.delay"></stringProp>
</ThreadGroup>
<hashTree>
<SubscriberSampler guiclass="JMSSubscriberGui" testclass="SubscriberSampler"
testname="LDAP" enabled="true">
<stringProp name="jms.jndi_properties">false</stringProp>
<stringProp
name="jms.initial_context_factory">com.sun.jndi.ldap.LdapCtxFactory</stringProp>
<stringProp name="jms.provider_url">ldap://localhost:389</stringProp>
<stringProp
name="jms.connection_factory">cn=TCF;dc=example;dc=com</stringProp>
<stringProp name="jms.topic">cn=T1;dc=example;dc=com</stringProp>
<stringProp name="jms.security_principle">cn=Directory Manager</stringProp>
<stringProp name="jms.security_credentials">admin</stringProp>
<stringProp name="jms.authenticate">Required</stringProp>
<stringProp name="jms.iterations"></stringProp>
<stringProp name="jms.read_response">true</stringProp>
<stringProp name="jms.client_choice">Use
TopicSubscriber.receive()</stringProp>
</SubscriberSampler>
4/27/2011 Page 16 of 30
<hashTree/>
</hashTree>
</hashTree>
</hashTree>
</jmeterTestPlan>
LoadRunner
Sender
/*
* LoadRunner Java script. (Build: 946)
*
* Script Description:
*
*/
import lrapi.lr;
import javax.jms.*;
import javax.naming.*;
import java.util.Hashtable;
Hashtable env;
Context ctx = null;
Session session=null;
Connection connection=null;
ConnectionFactory cf = null;
Destination destination=null;
MessageProducer producer=null;
// Store the environment variables that tell JNDI which initial context
// to use and where to find the provider.
env.put(Context.PROVIDER_URL,"ldap://localhost:389");
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory"
);
4/27/2011 Page 17 of 30
env.put(Context.SECURITY_PRINCIPAL,"cn=Directory Manager");
env.put(Context.SECURITY_CREDENTIALS,"admin");
try {
// Create the initial context.
ctx = new InitialContext(env);
} catch (NamingException ex) {
ex.printStackTrace();
System.exit(-1);
}
try {
// Lookup my connection factory from the admin object store.
// The name used here here must match the lookup name
// used when the admin object was stored.
cf = (javax.jms.ConnectionFactory)
ctx.lookup("cn=QCF;dc=example;dc=com");
System.out.println("Connection Factory object found.");
} catch (NamingException ne) {
System.err.println("Failed to lookup Connection Factory object.");
ne.printStackTrace();
System.exit(-1);
}
// create a consnection
connection = cf.createConnection();
// create a session
session = connection.createSession(
false /* not transacted */, Session.AUTO_ACKNOWLEDGE);
return 0;
}//end of init
try{
4/27/2011 Page 18 of 30
// create a producer
producer = session.createProducer(destination);
return 0;
}//end of action
return 0;
}//end of end
}
Receiver
/*
* LoadRunner Java script. (Build: 946)
*
* Script Description:
*
*/
4/27/2011 Page 19 of 30
import lrapi.lr;
import javax.jms.*;
import javax.naming.*;
import java.util.Hashtable;
Hashtable env;
Context ctx = null;
Session session=null;
Connection connection=null;
ConnectionFactory cf = null;
Destination destination=null;
MessageConsumer consumer=null;
public int init() throws Throwable {
env = new Hashtable();
// Store the environment variables that tell JNDI which initial context
// to use and where to find the provider.
env.put(Context.PROVIDER_URL,"ldap://localhost:389");
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory"
);
env.put(Context.SECURITY_PRINCIPAL,"cn=Directory Manager");
env.put(Context.SECURITY_CREDENTIALS,"admin");
try {
// Create the initial context.
ctx = new InitialContext(env);
} catch (NamingException ex) {
ex.printStackTrace();
System.exit(-1);
}
try {
// Lookup my connection factory from the admin object store.
// The name used here here must match the lookup name
// used when the admin object was stored.
cf = (javax.jms.ConnectionFactory)
ctx.lookup("cn=QCF;dc=example;dc=com");
System.out.println("Connection Factory object found.");
4/27/2011 Page 20 of 30
// create a consnection
connection = cf.createConnection();
// create a session
session = connection.createSession(
false /* not transacted */, Session.AUTO_ACKNOWLEDGE);
return 0;
}//end of init
try {
// create a consumer
consumer = session.createConsumer(destination);
System.out.println(m.getText());
return 0;
}//end of action
return 0;
}//end of end
}
Publisher
/*
* LoadRunner Java script. (Build: 946)
*
* Script Description:
*
*/
import lrapi.lr;
import javax.jms.*;
import javax.naming.*;
import java.util.Hashtable;
Hashtable env;
Context ctx = null;
Session session=null;
Connection connection=null;
ConnectionFactory cf = null;
Destination destination=null;
TopicPublisher publisher=null;
4/27/2011 Page 22 of 30
// Store the environment variables that tell JNDI which initial context
// to use and where to find the provider.
env.put(Context.PROVIDER_URL,"ldap://localhost:389");
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory"
);
env.put(Context.SECURITY_PRINCIPAL,"cn=Directory Manager");
env.put(Context.SECURITY_CREDENTIALS,"admin");
try {
// Create the initial context.
ctx = new InitialContext(env);
} catch (NamingException ex) {
ex.printStackTrace();
System.exit(-1);
}
try {
// Lookup my connection factory from the admin object store.
// The name used here here must match the lookup name
// used when the admin object was stored.
cf = (javax.jms.ConnectionFactory)
ctx.lookup("cn=TCF;dc=example;dc=com");
System.out.println("Connection Factory object found." +cf.toString());
} catch (NamingException ne) {
System.err.println("Failed to lookup Connection Factory object.");
ne.printStackTrace();
System.exit(-1);
}
// create a consnection
connection = cf.createConnection();
// create a session
session = connection.createSession(
false /* not transacted */, Session.AUTO_ACKNOWLEDGE);
return 0;
}//end of init
try{
// create a publisher
publisher = (TopicPublisher)session.createProducer(destination);
return 0;
}//end of action
publisher=null;
session=null;
connection=null;
4/27/2011 Page 24 of 30
return 0;
}//end of end
}
Subscriber
/*
* LoadRunner Java script. (Build: 946)
*
* Script Description:
*
*/
import lrapi.lr;
import javax.jms.*;
import javax.naming.*;
import java.util.Hashtable;
Hashtable env;
Context ctx = null;
Session session=null;
Connection connection=null;
ConnectionFactory cf = null;
Destination destination=null;
MessageConsumer consumer=null;
public int init() throws Throwable {
env = new Hashtable();
// Store the environment variables that tell JNDI which initial context
// to use and where to find the provider.
env.put(Context.PROVIDER_URL,"ldap://localhost:389");
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory"
);
env.put(Context.SECURITY_PRINCIPAL,"cn=Directory Manager");
env.put(Context.SECURITY_CREDENTIALS,"admin");
try {
// Create the initial context.
ctx = new InitialContext(env);
4/27/2011 Page 25 of 30
try {
// Lookup my connection factory from the admin object store.
// The name used here here must match the lookup name
// used when the admin object was stored.
cf = (javax.jms.ConnectionFactory)
ctx.lookup("cn=QCF;dc=example;dc=com");
System.out.println("Connection Factory object found.");
} catch (NamingException ne) {
System.err.println("Failed to lookup Connection Factory object.");
ne.printStackTrace();
System.exit(-1);
}
// create a consnection
connection = cf.createConnection();
// create a session
session = connection.createSession(
false /* not transacted */, Session.AUTO_ACKNOWLEDGE);
return 0;
}//end of init
try {
// create a consumer
consumer = session.createConsumer(destination);
connection.start();
System.out.println(m.getText());
return 0;
}//end of action
return 0;
}//end of end
}
Grinder
initialContext = InitialContext(properties)
connectionFactory = initialContext.lookup("cn=QCF;dc=example;dc=com")
queue = initialContext.lookup("cn=Q1;dc=example;dc=com")
initialContext.close()
# Create a connection.
connection = connectionFactory.createQueueConnection()
connection.start()
random = Random()
class TestRunner:
def __call__(self):
log = grinder.logger.output
sender = session.createSender(queue)
instrumentedSender = Test(1, "Send a message").wrap(sender)
initialContext = InitialContext(properties)
4/27/2011 Page 29 of 30
connectionFactory = initialContext.lookup("cn=QCF;dc=example;dc=com")
queue = initialContext.lookup("cn=Q1;dc=example;dc=com")
initialContext.close()
# Create a connection.
connection = connectionFactory.createQueueConnection()
connection.start()
#grinder.statistics.registerSummaryExpression(
# "Mean delivery time",
# "(/ userLong0(+ timedTests untimedTests))")
class TestRunner(MessageListener):
def __init__(self):
self.messageQueue = [] # Queue of received messages not yet recorded.
self.cv = Condition() # Used to synchronise thread activity.
def __call__(self):
log = grinder.logger.output
receiver = session.createReceiver(queue)
receiver.messageListener = self
log("Received message")
self.messageQueue.append(deliveryTime)
self.cv.notifyAll()
self.cv.release()