diff --git a/xdocs/extending/index.xml b/xdocs/extending/index.xml index a1a0893625..a9935dcc8c 100644 --- a/xdocs/extending/index.xml +++ b/xdocs/extending/index.xml @@ -18,8 +18,8 @@ to make this task easier.
  • Creating your own logic SamplerController
  • Creating your own test sample SamplerController
  • Creating your own Sampler
  • -
  • Making your custom elements saveable and loadable from within JMeter
  • Making your custom elements play nice as a JMeter UI component
  • +
  • Making your custom elements saveable and loadable from within JMeter
  • Creating your own Timer

    The timer interface:

    @@ -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.

    +

    Creating your own logic SamplerController

    +

    The SamplerController interface looks as follows:

    +
    +	  Entry nextEntry();
    +	  Collection getListeners();
    +	  void addSamplerController(SamplerController controller);
    +	  void addConfigElement(ConfigElement config);
    +	  Object clone();
    +
    +

    Again, clone() is a method that must be implemented to all SamplerControllers to avoid +contamination between sampling threads.

    +

    The nextEntry() 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. +

    +

    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 null. 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.

    +

    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 +

    +

    getListeners() 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.

    +

    addSamplerController(SamplerController controller) is the method used to +add sub controllers to your SamplerController.

    +

    addConfigElement(ConfigElement config) 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. +

    +

    Creating your own test sample SampleController

    +

    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.

    +

    Creating your own Sampler

    +

    The Sampler interface:

    +
    +	  public SampleResult sample(Entry e)
    +
    +

    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.

    +

    Making your custom elements play nice as a JMeter UI component

    +

    In order to take part in the JMeter UI, your component needs to implement the +JMeterComponentModel interface:

    +
    +	  Class getGuiClass();
    +	  public String getName();
    +	  public void setName(String name);
    +	  public Collection getAddList();
    +	  public String getClassLabel();
    +	  public void uncompile();
    +
    +

    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.

    +

    getAddList() 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).

    +

    uncompile 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.

    +

    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:

    +
    +	  void setModel(Object model);
    +	  public void updateGui();
    +
    +

    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 updateGui(), 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).

    +

    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 search_paths 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.

    +

    Making your custom elements saveable and loadable from within JMeter

    +

    The Saveable interface has just one method:

    +
    +	  public Class getTagHandlerClass()
    +
    +

    This method simply returns the Class object that represents the Class that handles +the saving and loading of your component.

    +

    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.

    +

    To write your own TagHandler, you will have to implement the following methods:

    +
    +	  public abstract void setAtts(Attributes atts) throws Exception
    +	  public String getPrimaryTagName()
    +	  public void save(Saveable objectToSave,Writer out) throws IOException
    +
    +

    getPrimaryTagName() 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.

    +

    setAtts(Attributes atts) 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.

    +

    save(Saveable objectToSave,Writer out) - 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.

    +

    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 +<tag-name>TagStart(Attributes atts), and for every end tag, JMeter calls +<tag-name>TagEnd().

    +

    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 +foo(Attributes atts) 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 foo_nestedFoo(Attributes atts) and +foo_nestedFoo(String data). And so on. +

    +

    An annotated example:

    +
    +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 <property> 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 <property>
    +	 * tag is encountered.
    +	 */
    +	public void property(String data)
    +	{
    +
    +		if(data != null && data.trim().length() > 0)
    +		{
    +			config.putProperty(currentProperty,data);
    +			currentProperty = null;
    +		}
    +	}
    +
    +	/**
    +	 * Called by reflection when the <property> 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("<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(</ConfigElement>");
    +	}
    +
    +	/**
    +	 * Routine to write each property to xml.
    +	 */
    +	private void writeProperty(Writer out,String key,Object value) throws IOException
    +	{
    +		out.write("<property name=\"");
    +		out.write(key);
    +		out.write("\">\n");
    +		JMeterHandler.writeObject(value,out);
    +		out.write("\n</property>\n");
    +	}
    +
    +

    +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. +

    +

    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. +

    +

    +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. +

    +

    +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. +

    diff --git a/xdocs/user_manual/index.xml b/xdocs/user_manual/index.xml index 0b560526c0..a2f4eff842 100644 --- a/xdocs/user_manual/index.xml +++ b/xdocs/user_manual/index.xml @@ -28,39 +28,39 @@ web applications, although JMeter is capable of testing most any type of server- application.

    -

    Overview

    +

    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.

    Most functions in the UI are available from popup menus that appear when you right-click on an element in the test tree.

    -The test configuration tree begins with two elements - TestPlan +The test configuration tree begins with two elements - TestPlan and WorkBench. The TestPlan element -will contain all the elements which make up your test. -The WorkBench is simply an area to store test elements while you +will contain all the elements which make up your test. +The WorkBench is simply an area to store test elements while you are in the process of constructing a test.

    -A TestPlan consists of one or more ThreadGroups. A +A TestPlan consists of one or more ThreadGroups. A ThreadGroup is a root element (it can not be nested) which may contain timers, -listeners, controllers, and config elements. A ThreadGroup also +listeners, controllers, and config elements. A ThreadGroup also defines the number of threads available to the threadgroup.