Obtained from:
Submitted by:
Reviewed by:


git-svn-id: https://svn.apache.org/repos/asf/jakarta/jmeter/trunk@322630 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Michael Stover 2001-03-24 14:39:59 +00:00
parent cee0c1d100
commit 9713a90204
2 changed files with 331 additions and 24 deletions

View File

@ -18,8 +18,8 @@ to make this task easier.
<li>Creating your own logic SamplerController</li>
<li>Creating your own test sample SamplerController</li>
<li>Creating your own Sampler</li>
<li>Making your custom elements saveable and loadable from within JMeter</li>
<li>Making your custom elements play nice as a JMeter UI component</li>
<li>Making your custom elements saveable and loadable from within JMeter</li>
</ul>
<h3>Creating your own Timer</h3>
<p>The timer interface:</p>
@ -95,6 +95,313 @@ on their own), and you may have to do casting and parsing. Example: an Integer
have to be converted from a String to an int, so your getXXX() method should check
for this possibility to avoid exceptions.
</p>
<h3>Creating your own logic SamplerController</h3>
<p>The SamplerController interface looks as follows:</p>
<pre>
Entry nextEntry();
Collection getListeners();
void addSamplerController(SamplerController controller);
void addConfigElement(ConfigElement config);
Object clone();
</pre>
<p>Again, <b>clone()</b> is a method that must be implemented to all SamplerControllers to avoid
contamination between sampling threads.</p>
<p>The <b>nextEntry()</b> method is the essential job of a SamplerController - to deliver
Entry objects to be sampled. An Entry object encapsulates all the information needed
by a Sampler to do its job. The nextEntry() method should work like an iterator and
continuously return new Entry objects.
</p>
<p>There are two boundary conditions that need to be handled. If the Controller has no
more Entries to give, for the rest of the test, it should return <b>null</b>. Therefore,
if your Controller has sub-controllers it is receiving Entries from, it should remove
them from its list of controllers to get Entries from. The other condition is when
your controller reaches the end of its list of Entries, and it needs to start over
from the beginning. The parent Controller needs to know this so that it can move
on to its next controller in its list. Therefore, at the end of each iteration,
your SamplerController needs to return a CycleEntry object instead of a normal Entry.
Conversely, this means that if your Controller receives a CycleEntry object, it should
move on to the next Controller in its list.</p>
<p>A logic controller does not generate Entries on its own, but simply regulates
the flow of Entries from its sub-controllers. A logic controller might provide
looping logic, or it might modify the Entries that pass through it, or whatever.
GenericController provides an implementation that does absolutely nothing but
pass Entries on from its sub-controllers. This class is useful both for reference
purposes and to extend, since it provides a lot of methods you're likely to find
useful
</p>
<p><b>getListeners()</b> is an odd member of this Class. It's there to serve those who
want their controller to receive sample data. This would be useful for a controller
that modified Entry objects based on previous sample results (like an HTML spider
that dynamically reacted to previously sampled webpages for links and forms). The
responsibility of the controller implementer is to collect all potential listeners
from the sub-controller list, and add themselves if desired. Most SamplerControllers
that extend GenericController don't have to do anything.</p>
<p><b>addSamplerController(SamplerController controller)</b> is the method used to
add sub controllers to your SamplerController. </p>
<p><b>addConfigElement(ConfigElement config)</b> Your SamplerController should also
be capable of holding configuration elements and adding them to Entries as they
pass through your controller. Again, see GenericController for reference. Essentially,
all Entry objects that get returned by nextEntry() are handed all the ConfigElements
of the controller.
</p>
<h3>Creating your own test sample SampleController</h3>
<p>A SamplerController that generates Entry objects is just like a logic controller
except that it creates its own Entry objects instead of gathering them from
sub-controllers (although, to be fully correct, your test sample SampleController
should handle both possibilities). Your test sample SampleController can also
benefit from extending GenericController. By doing so, most of your cloning and
saving needs are handled (but probably not entirely). See HttpTestSample as
reference.</p>
<h3>Creating your own Sampler</h3>
<p>The Sampler interface:</p>
<pre>
public SampleResult sample(Entry e)
</pre>
<p>Your Sampler has two responsibilities. Of lesser importance, it should do whatever
it is you want to do, given an Entry object that hopefully contains information
about what is to be sampled. Of greater importance, your sampler should return
a SampleResult object that holds information about the sampling. Information such
as how long the sample took, the text response from the sample (if appropriate), and
a string that describes the location of what was sampled. The SampleResult interface
is essentially a Map with public static Strings as keys. </p>
<h3>Making your custom elements play nice as a JMeter UI component</h3>
<p>In order to take part in the JMeter UI, your component needs to implement the
JMeterComponentModel interface:</p>
<pre>
Class getGuiClass();
public String getName();
public void setName(String name);
public Collection getAddList();
public String getClassLabel();
public void uncompile();
</pre>
<p>Most of this stuff is easy, boring, and tedious. getName(), setName() is a simple
String property that is the name of the object. getClassLabel() should return
a String that describes the class. This string will be displayed to the user and
so should be short but meaningful. getGuiClass() should return a Class object for
the class that will be used as a GUI component. This class should be a subclass
of java.awt.Container, and preferably a subclass of javax.swing.JComponent.</p>
<p><b>getAddList()</b> should return a list of either Strings or JMenus. These Strings
represent the Classes that can be added to your SamplerController. Each String
should correspond to the target class's getClassLabel() String. MenuFactory is
a class that will return some preset menu lists (such as all available SamplerControllers,
all available ConfigElements, etc).</p>
<p><b>uncompile</b> is a cleanup method used between sampling runs. When the user
hits "Start", JMeter "compiles" the objects in the tree. Child nodes are added
to their parent objects recursively until there is one TestPlan object, which is
then submitted for testing. Afterward, these elements have to un-added from their
parent objects, or uncompiled. To uncompile your class, simply clear all your
data structures that are holding sub-elements. For your SamplerController, this
will be the list of sub-controllers and the list of ConfigElements.</p>
<p>That's it, except for your GUI class. If your SamplerController has no
configuration needs, just return org.apache.jmeter.gui.NamePanel, and the user will
at least be able to change the name of your component. Otherwise, create a gui class
that implements the ModelSupported interface:</p>
<pre>
void setModel(Object model);
public void updateGui();
</pre>
<p>setModel is used to hand your JMeterModelComponent class to the GUI class when
it is instantiated. It is your responsibility for providing the means by which
the Gui class updates the values in the model class. For updating in the other
direction, there is <b>updateGui()</b>, which the model class can call if necessary.
Note, normally, this call is made for you automatically whenever the Gui is brought
to the screen. If you are creating a Visualizer, then you may need to use updateGui().
For reference, refer to UrlConfigGui (in org.apache.jmeter.protocol.http.config.gui).</p>
<p>If you have done all this correctly, there's just one more step. If you compile
your classes into the ApacheJMeter.jar file, then you're done. Your classes will
be automatically found and used. Otherwise, you will need to modify jmeter.properties.
The <i>search_paths</i> property should be modified to include the path where your
classes are. This does not obviate the need for your classes to be in the JVM's
CLASSPATH - it is an additional requirement. Otherwise, your classes will be
detected, and the Gui will not make them available to the user.</p>
<h3>Making your custom elements saveable and loadable from within JMeter</h3>
<p>The Saveable interface has just one method:</p>
<pre>
public Class getTagHandlerClass()
</pre>
<p>This method simply returns the Class object that represents the Class that handles
the saving and loading of your component.</p>
<p>To write this SaveHandler, make a class that extends TagHandler
(from org.apache.jmeter.save.xml). Note, if your component extends AbstractConfigElement,
you don't need to do this - provided you only need to save information stored in
the Map from AbstractConfigElement.</p>
<p>To write your own TagHandler, you will have to implement the following methods:</p>
<pre>
public abstract void setAtts(Attributes atts) throws Exception
public String getPrimaryTagName()
public void save(Saveable objectToSave,Writer out) throws IOException
</pre>
<p><b>getPrimaryTagName()</b> should return the String that is the XML tagname that your
class handles. When you save your object, it should all be contained within an
XML tag of the same name. This will ensure that when JMeter's parser hits that tag,
your class will be called upon to handle the data.</p>
<p><b>setAtts(Attributes atts)</b> is called when the parser first hits your tag.
If this primary tag has any attributes, this method represents your chance to save
the information.</p>
<p><b>save(Saveable objectToSave,Writer out)</b> - when the user selects "Save",
JMeter will call this method and hand the Saveable object to be saved (it will be
the object that specified your TagHandler as the class responsible for it's saving).
This method should use the given Writer object to print all the XML necessary to
save the current state of the objectToSave.</p>
<p>There's more you have to do to handle creating a new Object when JMeter parses
an XML file. However, there's no standard interface you need to implement, but rather,
JMeter uses reflection to generate method calls into your class. When JMeter hits
a tag that corresponds to your PrimaryTagName, an instance of your TagHandler will
be created, and it's setAtts() method will get called. Thereafter, methods are called
depending on subsequent tags and character data. For every tag, JMeter calls
&lt;tag-name&gt;TagStart(Attributes atts), and for every end tag, JMeter calls
&lt;tag-name&gt;TagEnd().</p>
<p>Additionally, JMeter will call a method that corresponds to all tags that are
current. So, for instance, if JMeter runs into a tag name "foo", then
<b>foo(Attributes atts)</b> will be called. If JMeter then parses character data,
then foo(String data) will be called. If JMeter parses a tag within foo, called
"nestedFoo", then JMeter will call <b>foo_nestedFoo(Attributes atts)</b> and
<b>foo_nestedFoo(String data)</b>. And so on.
</p>
<p>An annotated example:</p>
<pre>
public class AbstractConfigElementHandler extends TagHandler
{
private AbstractConfigElement config;
private String currentProperty;
public AbstractConfigElementHandler()
{
}
/**
* Returns the AbstractConfigElement object parsed from the XML. This method
* is required to fulfill the SaveHandler interface. It is used by the XML
* routines to gather all the saved objects.
*/
public Object getModel()
{
return config;
}
/**
* This is called when a tag is first encountered for this handler class to handle.
* The attributes of the tag are passed, and the SaveHandler object is expected
* to instantiate a new object.
*/
public void setAtts(Attributes atts) throws Exception
{
String className = atts.getValue("type");
config = (AbstractConfigElement)Class.forName(className).newInstance();
}
/**
* Called by reflection when a &lt;property&gt; tag is encountered. Again, the
* attributes are passed.
*/
public void property(Attributes atts)
{
currentProperty = atts.getValue("name");
}
/**
* Called by reflection when text between the begin and end &lt;property&gt;
* tag is encountered.
*/
public void property(String data)
{
if(data != null &amp;&amp; data.trim().length() &gt; 0)
{
config.putProperty(currentProperty,data);
currentProperty = null;
}
}
/**
* Called by reflection when the &lt;property&gt; tag is ended.
*/
public void propertyTagEnd()
{
// Here's a tricky bit. See below for explanation.
List children = xmlParent.takeChildObjects(this);
if(children.size() == 1)
{
config.putProperty(currentProperty,((TagHandler)children.get(0)).getModel());
}
}
/**
* Gets the tag name that will trigger the use of this object's TagHandler.
*/
public String getPrimaryTagName()
{
return "ConfigElement";
}
/**
* Tells the object to save itself to the given output stream.
*/
public void save(Saveable obj,Writer out) throws IOException
{
AbstractConfigElement saved = (AbstractConfigElement)obj;
out.write("&lt;ConfigElement type=\"");
out.write(saved.getClass().getName());
out.write("\">\n");
Iterator iter = saved.getPropertyNames().iterator();
while (iter.hasNext())
{
String key = (String)iter.next();
Object value = saved.getProperty(key);
writeProperty(out,key,value);
}
out.write(&lt;/ConfigElement&gt;");
}
/**
* Routine to write each property to xml.
*/
private void writeProperty(Writer out,String key,Object value) throws IOException
{
out.write("&lt;property name=\"");
out.write(key);
out.write("\">\n");
JMeterHandler.writeObject(value,out);
out.write("\n&lt;/property&gt;\n");
}
</pre>
<p>
In the propertyTagEnd method, takeChildObjects() is called on the xmlParent
instance variable. xmlParent is inherited from TagHandler. It is the DocumentHandler
object that is running the show. xmlParent takes an XML file that represents a portion of
the test configuration tree, and recreates a tree-like data structure. When it is
done, it will convert its tree-like data structure into the test configuration tree
structure.
</p>
<p>However, sometimes, a tree element has sub objects that you do not want represented
in the tree - rather, they are objects that are part of your object. But, they may
be complicated enough to warrant their own SaveHandler class, and thus, the xmlParent
picks them up as part of its tree. When the tag is done, and you know that there are
child objects you want to grab, you can call the "takeChildObjects" method and get a
List object containing them all. This will remove them from the tree, and you can add
them to your object that you're creating.
</p>
<p>
UrlConfig is good example. It extends AbstractConfigElement, so it uses exactly the
code above to save and reload itself from XML. However, one of the pieces of data
that UrlConfig stores is an Arguments object. Arguments is too complicated to save
to file as a simple string, so it has its own Handler object (ArgumentsHandler). In
the above code, when the call to JMeterHandler.writeObject(value,out) is made, the
writeObject method detects whether the object implements Saveable, and if so, calls
the object's SaveHandler class to handle saving it. This means, however, that when
reading that XML file, the Argument object will show up as a separate entity in
the data tree, whereas it originally was just part of the data of the UrlConfig
object. In order to preserve that relationship, it's necessary for the
AbstractConfigElementHandler to check after each property tag is done for child
objects in the tree, and take them for its own use.
</p>
<p>
Please study the other SaveHandler objects and the TagHandler class to learn more
about how saving is accomplished. Once you understand the design, writing your
own SaveHandler is very easy.
</p>
</section>
</body>

View File

@ -28,39 +28,39 @@ web applications, although JMeter is capable of testing most any type of server-
application.
</i>
</p>
<p>
<a NAME="overview"></a>
<H2>Overview</H2>
<p>
JMeter 1.6 has a new UI layout. The window is divided into two sections. On the left is
a tree which represents a test configuration. The tree represents both
the hierarchical and ordered nature of the test. A test can be made up of
one or many subtests and each of these subtests may have a particular
a tree which represents a test configuration. The tree represents both
the hierarchical and ordered nature of the test. A test can be made up of
one or many subtests and each of these subtests may have a particular
ordering.
The main display is on the right side of the window.
Whenever an element in the tree is selected, its control panel is shown in
The main display is on the right side of the window.
Whenever an element in the tree is selected, its control panel is shown in
the main display allowing you to enter your test data.
When a visualizer is selected the main display will contain the
visualizer's view of the current test.
When a visualizer is selected the main display will contain the
visualizer's view of the current test.</p>
<table border="5"><tr><td><b>Most functions in the UI are available from popup menus
that appear when you right-click on an element in the test tree.</b></td></tr></table>
<p>
The test configuration tree begins with two elements - <b>TestPlan</b>
The test configuration tree begins with two elements - <b>TestPlan</b>
and <b>WorkBench</b>. The <b>TestPlan</b> element
will contain all the elements which make up your test.
The <b>WorkBench</b> is simply an area to store test elements while you
will contain all the elements which make up your test.
The <b>WorkBench</b> is simply an area to store test elements while you
are in the process of constructing a test.
</p>
<p>
A <b>TestPlan</b> consists of one or more <b>ThreadGroups</b>. A
A <b>TestPlan</b> consists of one or more <b>ThreadGroups</b>. A
<b>ThreadGroup</b> is a root element (it can not be nested) which may contain <b>timers</b>,
<b>listeners</b>, <b>controllers</b>, and <b>config elements</b>. A <b>ThreadGroup</b> also
<b>listeners</b>, <b>controllers</b>, and <b>config elements</b>. A <b>ThreadGroup</b> also
defines the number of threads available to the threadgroup.
</p>
<ul>
<li>A <b>timer</b> is a simple element that controls how long JMeter should delay between each test
sample when it runs. This allows JMeter to simulate human actions more closely.
Timer element's are leaves in the test tree they can not contain
sample when it runs. This allows JMeter to simulate human actions more closely.
Timer element's are leaves in the test tree they can not contain
sub-elements.
</li>
<li>A <b>listener</b> receives information about response data while JMeter runs. For instance, during testing
@ -70,16 +70,16 @@ are visualizers (represent the data visually in the main window), or reporters (
to file). Listeners are also leaves in a test configuration tree.
</li>
<li>A <b>controller</b> is an element that controls the flow of test samples. It also controls the process by which
test samples are created. Controllers implement JMeter's various testing
test samples are created. Controllers implement JMeter's various testing
protocols. They may have other controllers and/or config elements as
sub-elements.
</li>
<li>A <b>Config Element</b> represents a coherent set of information that is
<li>A <b>Config Element</b> represents a coherent set of information that is
usually specifically targeted to a particular
protocol or controller. For instance, setting up a database test requires three
config elements - one to configure the basic information about the database (what host,
protocol or controller. For instance, setting up a database test requires three
config elements - one to configure the basic information about the database (what host,
what driver, login and password to use), one to configure the SQL query
to be tested, and one to configure the pool of database connections (how many connections
to be tested, and one to configure the pool of database connections (how many connections
to store in pool, etc).
Config Elements are leaves in the test configuration tree.
</li>
@ -93,9 +93,9 @@ all of its ThreadGroups to the engine. The engine creates threads, and each thr
iterates through the test cases.
</p>
<p>
When a test is run, every element in the tree receives every element that
is above it in the test configuration. That is a Timer inserted into the
test configuration tree at the highest level will apply to every element
When a test is run, every element in the tree receives every element that
is above it in the test configuration. That is a Timer inserted into the
test configuration tree at the highest level will apply to every element
that lies below it. This is why it makes sense to add a URL Config Element to the
ThreadGroup in addition to a Web Test Controller with multiple test samples. If the
top level config element has only a host name, the host name will be applied to all