Custom Components

User rating:
2.7 (7 ratings)
Article Index
0.1. Introduction
0.2. Getting Started
0.3. The Anatomy of a Component
0.4. Properties
0.4.1. Distributed Properties
0.4.2. Supported types
0.5. Terminals
0.5.1. Terminal Messages
0.5.2. Message Signatures
0.6. Controls and Displays
0.7. The Canvas
0.7.1. Canvas State
0.7.2. The Project Canvas
0.7.3. The VU Scenario Canvas
0.7.4. Distribution consequences
0.8. Statistics
0.9. Statistic Variables
0.10. Statistics Writers
0.11. Statistics
0.12. Groovy Component Reference
0.13. The Component Script header
0.14. Defining Component Terminals and Properties
0.14.1. Terminals and messages
0.14.2. Properties
0.14.3. Attributes
0.15. Component handlers
0.15.1. Property handlers
0.15.1.1. onReplace
0.15.2. Terminal related handlers
0.15.2.1. onMessage
0.15.2.2. onConnect
0.15.2.3. onDisconnect
0.15.2.4. onSignature
0.15.2.5. likes
0.15.3. Other handlers
0.15.3.1. onAction
0.15.3.2. onRelease
0.15.3.3. collectStatisticsData
0.15.3.4. handleStatisticsData
0.15.3.5. generateSummary
0.16. Component Categories
0.16.1. Misc
0.16.2. Analytics
0.16.3. Flow Control
0.16.4. Output
0.16.5. Runners
0.16.6. Scheduler
0.16.7. Generators
0.17. Custom Statistics
0.18. Creating a Statistic Variable
0.19. Statistic Writers
0.19.1. MINMAX
0.19.2. SAMPLE
0.19.3. THROUGHPUT
0.19.4. VARIABLE
0.19.5. COUNTER
0.20. Component Layout
0.20.1. Containers
0.20.2. Controls
0.20.2.1. property
0.20.2.2. node
0.20.2.3. label
0.20.2.4. action
0.20.2.5. separator
0.21. Settings
0.22. Utilities
0.22.1. Logging
0.22.2. Scheduling tasks
0.22.2.1. Executor
0.22.2.2. Test Execution Phases
0.22.3. Aggregating distributed values

0.1. Introduction

In LoadUI, almost all of the actual work is done by Components. Each component has a specific task, such as generating load, or sending a web request. We see Components as being one of the main points for extending the functionality of LoadUI, and have taken many steps toward making this process easy to do; We have created a Component framework, and a Groovy domain specific language (DSL) which both serve to make Component development easy to get started with, yet powerful enough to suite our needs. Designing custom components does require programming though, and this document is targeted towards people who have prior experience with the Groovy programming language. It is also assumed that you are familiar with the LoadUI tool itself.

Get more componentsPlease note that all the default components included with LoadUI except those related to soapUI are created using the below described infrastructure; they are easily accessible in the /script-components folder where you can read and modify their source code as desired. You may also want to have a look at other user-contributed components, accessible by clicking on Get more components at the bottom of the Component Toolbar in LoadUI. RunningĀ loadUI.bat instead of LoadUI.exe will give you more (and more useful) error messages when your code for example doesn't parse correctly. Running loadUItest.bat currently requires you to have the JavaFX SDK installed, but we hope to be able to remove this requirement in the future.

0.2. Getting Started


If you are eager to create your own components there are some examples that should be interesting:

  1. Custom components video is a 15 minute long video showing how custom components works in LoadUI.
  2. Extending LoadUI - Part 1 shows you how to create a simple component for "dropping" trigger messages (to simulate falloff).
  3. Extending LoadUI - Part 2 shows you how to create a component that adds data to messages to be used by other components.
  4. Extending LoadUI - Part 3 shows you how to create a full-blown component with reporting, layouts, icon, etc.
  5. A bare-bones Process Runner component for LoadUI shows a simple runner component for running external processes.
  6. Creating a new load generator in LoadUI

0.3. The Anatomy of a Component

0.4. Properties

Properties can be seen as values connected to the Component. A Component may have zero or more Properties, and each Property has a name, a type, and a value. The name of a Property is a String, and the type is one of the supported classes, such as Integer, String, or File. The value of a Property is an object of the Properties type. Properties can be seen as more advanced class fields, which you may be familiar with from a Java or Groovy background.

0.4.1. Distributed Properties

Since LoadUI allows distributed testing, a single Component may exist in a number of different VU Scenarios simultaneously. It is important to know that Properties are fully synchronized between these instances by default, so a change made to a Property on a component will be propagated to its other distributed instances. For example: A Web Page Runner has a number of Properties, one being the target URL (the type of the Property is String). This Property is made available to the user as a text field on the Component surface. When changed, the Property value propagates to any agents that are assigned to the Web Page Runners VU Scenario, so each agent is targeting the same URL. Note that any other Web Page Runners in the Canvas have their own URL Property, which can have a completely different value.

0.4.2. Supported types

Currently, the supported types for a Property are:

  • String
  • Boolean
  • Numeric types (Integer, Long, Float, Double, etc...)
  • File (files are synchronized to Agents, but directories currently are not supported)

Property values are saved when the Components Project is saved, and loaded again when the Project is loaded.

0.5. Terminals

Terminals are the main way for a Component to transmit and receive data. Terminals are visible to the user, and the user can freely connect different Terminals between Components. There are two types of Terminals, InputTerminals and OutputTerminals. InputTerminals are for receiving data, their only function is to react to incoming data. The other type of Terminal is the OutputTerminal, which is used to send data out from the Component. OutputTerminals may also optionally provide a message signature, which is may or may not abide to (described later in this section). The Component is also able to react to events generated when connections are made or broken, involving one of its Terminals.

0.5.1. Terminal Messages

The data that is transmitted between Terminals are called Terminal Messages. These messages can be seen as String-to-Object maps; Each message potentially containing several key-value mappings. A TerminalMessage can also be empty, containing no data at all. To see what data is contained in a message, you may wish to experiment with some Components and the Table log Component, which displays each received message as a row in a Table; Each column in the row representing a key in the message.

0.5.2. Message Signatures

Some Components may want to know ahead of time what to expect to be contained in a message that it receives. For instance, the Condition Component in LoadUI in its basic mode needs to know which values will be available, so that the user can choose between them. This is done by specifying a Message Signature for the OutputTerminal. The format of a Message Signature is similar to that of a Terminal Message, but instead of holding actual values, it holds the types of the values. For example, an OutputTerminal which outputs messages of the form: { url:"http://example.com", status:200 }, may have the signature: { url:String, status:Integer }. Message signatures are optional, and not in any way enforced. Terminal messages may contain fields that are not defined in the signature, and may likewise be missing fields that are defined.

0.6. Controls and Displays

Besides being able to connect different Components by their Terminals, the user needs to be able to interact with and configure the Component. The Component usually also needs to display some information to the user. To do this there are a number of predefined Controls, such as form fields, knobs, switches, etc. that can be used to display information, or to allow the user to set Properties or to trigger Actions.

0.7. The Canvas

The Canvas is the workspace which holds the Component, and all its Connections to other Components. There are two different types of Canvases, the Project Canvas and the VU Scenario Canvas. For the most part, these two look and function in the same way, but they have some important differences.

0.7.1. Canvas State

Each Canvas can either be in a stopped or a running state. Changing this state generates an event which can be detected by the Components within the Canvas. Each Component may still function when the Canvas is stopped, doing any work it needs to do, but no Terminal Messages will be send or received when the Canvas is stopped. Passive components, like Runners, which only react to incoming Terminal Messages to do some work, may utilize this fact to ignore the Canvas state, since if a message is received the Canvas must be running, and work should thus be done.

0.7.2. The Project Canvas

Each Project has a single Project level Canvas, which cannot be distributed to Agents. Besides containing Components and Connections, the Project Canvas can contain VU Scenarios, which each have their own Canvas.

0.7.3. The VU Scenario Canvas

The VU Scenario Canvas is a bit more advanced, and depending on the Component, some things need to be taken into account when developing a Component for it to function properly in the VU Scenario Canvas. The most notable change for the VU Scenario Canvas is that it can be distributed to run on several LoadUI Agents, causing there to be multiple copies of one VU Scenario, and each Component within. This is only an issue when LoadUI is in distributed mode; When in local mode the VU Scenario Canvas functions just as the Project Canvas does.

0.7.4. Distribution consequences

When LoadUI is in distributed mode, execution of a VU Scenario runs on any Agents assigned to it, and not in the main LoadUI program. However, all user interaction still occurs locally, and any displays showing status of the component must be shown locally. This is a pretty complex situation, and may require rather complicated behavior from the Component to function as intended. Fortunately we've added some things to the framework to simplify this as much as possible.

0.8. Statistics

Each Component is capable of providing a number of statistical values which can be viewed in graph form and later analyzed. The user can also create assertions based on these values.

0.9. Statistic Variables

Each Component can have any number of Statistic Variables. These are raw values which the Component should be constantly updating. For example, All Runner Components expose TimeTaken, ResponseSize and Throughput Statistic Variables. The TimeTaken Variable is updated after each request has completed, with the time that the request took measured in milliseconds. Likewise, the ResponseSize Variable is updated with the response size of each completed request, measured in number of bytes. The Throughput Variable is updated with the same data as the ResponseSize Variable, but the Statistics exposed are quite different. This is due to the different Statistics Writers assigned to each Variable.

0.10. Statistics Writers

A Statistic Variable alone does not export any data which can be viewed in a graph. For it to do anything useful we have to assign one or more Statistics Writers to it. These take the raw values which are being fed into the Variable, and perform calculations on them to generate the Statistics which we can then view. Each Statistics Writer assigned to a Statistic Variable exposes one or more Statistics.

0.11. Statistics

Statistics are the actual processed values which can be used for analysis. They can for instance be plotted as a lines in a chart, exported as a table of data, or used in Assertions. Examples of Statistics include AVERAGE, MEDIAN, TPS, and STD_DEV. All these are calculated based on the data provided by the Components to the Statistic Variables. They are recorded for later use, and can be compared between different runs of a test.

0.12. Groovy Component Reference

Though is it definitely possible to write a LoadUI Component in Java, the easiest way to get started is using our Groovy Component framework. To get started, simply create a .groovy file in the script-components directory of your LoadUI installation.

0.13. The Component Script header

Though optional, it is recommended that you start your script with a script header, which provides some metadata about the Component, such as its name, type, etc. The format for the header is to start with "/**" and end with " */" (without the quotes). These should be on lines by themselves, and each line in-between is considered part of the header, and they should begin with " * ". Each header line may either have an attribute, or a line of text which will be used for the Component description. For example:

    /**
     * This is the description of my Component.
     * It can be multi-line, no problem!
     *
     * @name MyComponent
     */

The available header attributes are:

  • id [id]
    • The unique ID of the Component, defaults to the name of the script file, without the extension. This is useful when updating a component so that any earlier instances get updated as well.
    • Example: @id com.eviware.MyComponent
  • classloader [classloaderId]
    • The ID of the classloader for loading dependencies. Defaults to the id of the component. In certain situations, you may want multiple components to share the same classloader, in which case they should override this.
    • Example: @classloader com.eviware
  • name [name]
    • The name of the Component, defaults to the name of the script file, without the extension.
    • Example: @name My Component
  • icon [icon]
    • The icon file to use for the Component, defaults to the name of the script file, but with a .png extension instead of .groovy.
    • Example: @icon mycomponenticon.png
  • help [url]
    • Provides a URL to a help page for the Component. This page will be opened when the Components help button is pressed.
    • Example: @help http://www.loadui.org/Developers-Corner/custom-component-reference-new.htm
  • m2repo [url]
    • Adds a Maven2 repository for fetching dependencies from. Can be used multiple times in a single script header.
    • Example: @m2repo http://example.com/repository
  • dependency [groupId:artifactId:version]
    • Adds a dependency to the Component, checking the available repositories for the artifact. Can be used multiple times in a single script header.
    • Example: @dependency org.apache.httpcomponents:httpclient:4.1-alpha2
  • nonBlocking [boolean]
    • States that the Component handlers do not block. If set to true, all event handlers will be invoked in the main event thread, instead of in a separate worker thread. This has the advantage of making the component effectively single threaded, so concurrency issues can be ignored. HOWEVER! While in one of the handlers, large parts of LoadUI will be blocked, and it is thus VERY IMPORTANT not to block or perform long running operations in any of the handlers when setting the component to be nonBlocking. Defaults to false.
    • Example: @nonBlocking true
  • category [category]
    • Sets the category for the component. The category can define Terminals, Properties and other behavior, so be sure to study the different categories before selecting something other than misc. Defaults to misc. See the Component Categories section for more details.
    • Example: @category runner
  • deprecated
    • Marks the Component as deprecated. Deprecated components will not show up in the user interface, but any existing instances will continue to function.

0.14. Defining Component Terminals and Properties

After the header, you should define any Properties and Terminals that the Component should have.

0.14.1. Terminals and messages

To define a terminal, use the createInput and createOutput commands. createInput defines an InputTerminal, and createOutput defines an OutputTerminal, unsurprisingly. Each of these commands takes one, two or three arguments. The first argument, which is required, is a unique (for the Component) Terminal name (the created Terminal can be accessed later on in the script using this name). The second argmuent, which is optional, is a label which is shown in the interface. The third argument, which is also optional, is a description of the Terminal.

For example:

    createOutput('myTerminal', 'My Terminal', 'Description of my Terminal')

or

    createInput('myOtherTerminal') //No description required.

Setting the label or description for a Terminal can also be done after creation, for example:

    createInput('myTerminal', 'My Terminal', 'Description of My Terminal')

is equivalent to:

    createInput('myTerminal')
    myTerminal.label = 'My Terminal'
    myTerminal.description = 'Description of My Terminal'

The component can at any time create and send a terminal message out from one of its OutputTerminals. One Terminal Message can be sent multiple times, and even modified between sends. Terminal Messages are created using the command newMessage(), and can be modified like a Map in Groovy. Finally, messages are sent using the send command, which takes the OutputTerminal and the message as arguments.

For example:

    createOutput('myOutput')

    def message = newMessage()
    message['responseTime'] = 500

    send( myOutput, message )

0.14.2. Properties

A Property is defined using the createProperty command, which takes between two and four arguments. The first argument, which is required, is a unique (for the Component) Property name. The second argument, also required, is the value type of the Property. This needs to be one of the supported Property types, or unexpected problems may occur. The third argument, which is optional, is the default value of the Property. Note that the Component script is executed whenever the Component is created, which also occurs when a Project is loaded, so a Property may have a saved value, or it may be null. The default value is only used when the Component is first created, or if the Property doesn't already exist. The fourth argument, also optional, specifies whether the Property should propagate between the loadUI controller and agents or not. By default, this is true. Once created, Properties may be referenced using their given name as a variable name.

For example:

    createProperty('url', String, 'http://example.com') //String Property with a default value
    createProperty('outputFile', File) //Will initialize to null
    createProperty('localProperty', Long, 15, false) //Property that will not propagate to agents.

Once created, Properties may be referenced using their given name as a variable name. Their value may be set or read using property.value.

For example:

    createProperty('url', String)
    if( url.value == null ) {
        url.value = "http://example.com"
    }

0.14.3. Attributes

Attributes are like a form of lightweight Properties. Each Component has Attributes which can be get and set. Unlike Properties, Attributes can only have String values, change handlers cannot be added, and changes do not propagate to/from agents. There are two methods used to interact with attibutes: setAttribute, and getAttibute. The setAttribute method takes two Strings as arguments, an Attribute name, and the Attribute value. The getAttribute method also takes two Strings as arguments. The Attribute name, as well as a default value to return if the Attribute does not exist.

For example:

    setAttribute('myAttribute', 'This is my value') //Sets the attribute
    getAttribute('myAttribute', 'Default value') //Returns the previously set value
    getAttribute('otherAttribute', 'Default value') //Returns 'Default value', as the attribute does not exist.

0.15. Component handlers

The Terminals and Properties define the model of the Component, but the handlers define the actual behavior. There are a number of different handlers that the Component script can choose to implement, which is done using specially named Groovy Closures.

0.15.1. Property handlers

0.15.1.1. onReplace

The onReplace handler for a Property gets called whenever a Property value is changed. It is invoked with the new value of the Property, as well as the old value. One onReplace handler can be set per Property, and the syntax is as follows:

    onReplace( myProperty ) { newValue, oldValue ->
        log.info( "myProperty changed from $oldValue to $newValue" )
    }

You can also set the onReplace handler for a Property directly when creating the Property:

    createProperty( 'url', String ) { newValue, oldValue ->
        log.info( "url changed from $oldValue to $newValue" )
    }

Once the Component has initialized (after creation, or after it has been loaded from a saved state) each onReplace handler will be called once with the initial/stored value of the Property.

0.15.2.1. onMessage

The onMessage handler is invoked whenever a message is received on one of the defined InputTerminals. It is invoked with three arguments: The OutputTerminal which sent the message, the InputTerminal receiving the message, and the message itself. Note that the OutputTerminal generally won't be a Terminal attached to this Component, but rather most likely belongs to a completely different Component.

For example:

    onMessage = { outgoing, incoming, message ->
        log.info("InputTerminal $incoming just got a message: $message from the OutputTerminal $outgoing")
        send( myOutput, message ) //Forward the message to another Terminal.
    }

0.15.2.2. onConnect

The onConnect handler is invoked whenever a Connection is created involving one of our Terminals as an endpoint. For instance, the user has connected another Components OutputTerminal to one of our Components InputTerminals, or one of our Components OutputTerminals have been connected to something else. It is invoked with two arguments: The OutputTerminal, and the InputTerminal.

For example:

    onConnect = { output, input ->
        log.info("$output was just connected to $input.")
        if( output == myOutput ) log.info "My OutputTerminal was connected to something!"
    }

0.15.2.3. onDisconnect

The onDisconnect handler is similar to the onConnect handler, but invoked when a Connection involving one of our Terminals is removed. It is invoked with the same arguments as the onConnect handler.

For example:

    onDisconnect = { output, input ->
        log.info("$output and $input are no longer connected.")
    }

0.15.2.4. onSignature

The onSignature handler is invoked whenever the Message Signature of an OutputTerminal which is connected to one of our Componets InputTerminals changes. It is invoked with two arguments: The OutputTerminal which is changing its signature, and the signature itself.

For example:

    onSignature = { output, signature ->
        log.info("$output has changed its signature to $signature.")
    }

0.15.2.5. likes

The likes handler is invoked by the user interface when it needs to determine if a particular OutputTerminal is liked by the InputTerminal. This closure is invoked with an OutputTerminal, so the closure may use things like the terminals name or signature to decide whether to like it or not. This informatino is used by the user interface to give hints as to which Terminals are likely to give desired results when connected (e.g. connecting the Trigger Signal Terminal from a Generator to the Trigger Input Terminal of a Runner). One likes handler can be set per InputTerminal, and the syntax is as follows:

    likes( myTerminal ) { outputTerminal ->
        outputTerminal.name == 'triggerTerminal'
    }

You can also set the likes handler for an InputTerminal directly when creating the Terminal:

    createInput( 'myTerminal', 'My Terminal' ) { outputTerminal ->
        outputTerminal.messageSignature.values().any { Number.isAssignableFrom( it ) }
    }

0.15.3. Other handlers

0.15.3.1. onAction

Components can react to different action events which are fired during runtime, for instance when starting or stopping a test. Each action is identified by a String, and each can have an onAction handler. For example:

    onAction( "START" ) {
        log.info( "The test just started!" )
    }

    onAction( "STOP" ) {
        log.info( "The test just stopped!" )
    }

0.15.3.2. onRelease

The onRelease handler is invoked when the Component is unloaded. It is important that the Component releases any resources that it is holding, and stops any Threads that it may have started.

For example:

    def file = new File( ... )

    ...

    onRelease = {
        file.close()
    }

0.15.3.3. collectStatisticsData

    //TODO

0.15.3.4. handleStatisticsData

    //TODO

0.15.3.5. generateSummary

    //TODO

0.16. Component Categories

There are a number of different predefined categories for Components, which predetermine some of their behavior. This can be in the form of adding Properties and Terminals, or providing some default event handlers. In short it provides a sort of base template for the Component, and thus it is important to know what the Category does, and what it expects the component script to do.

0.16.1. Misc

@category misc

The misc category is the default one, and it provides a blank template for creating a Component. It doesn't define any Properties, Terminals or Handlers.

0.16.2. Analytics

@category analytics

The analytics category defines a base for reacting to incoming messages and analyzing them. It defines a single InputTerminal, named inputTerminal, which is used as an input for messages to be analyzed. It also defines an onMessage handler, so any Component script using this category should not override this. Instead, it provedes another handler to override, the analyse handler, which is invoked with a single argument: The Terminal Message to analyze. This handler must be implemented by the Component script.

Overridable handlers defined by the analytics category:

    analyze { message ->
        log.info("Analyze the contents of $message here.")
    }

0.16.3. Flow Control

@category flow

The flow control category defines a base for Components which direct incoming messages to one of several OutputTerminals. It defines a single InputTerminal, named incomingTerminal, and a list of OutputTerminals to direct messages to, which is initially empty. The list is available to the script as outgoingTerminalList, and the number of OutputTerminals can be modified by using the two commands createOutgoing() and deleteOutgoing(), neither of which take any arguments. OutputTerminals are removed in a LIFO order.

Using the OutgoingTerminalsList:

    //Create two OutputTerminals.
    createOutgoing()
    createOutgoing()

    def i = 0

    //Dispatch messages to the two OutputTerminals in a round robin fashion.
    onMessage = { outgoing, incoming, message ->
        if( incoming == incomingTerminal ) {
            if( i++ % 2 == 0) send( outgoingTerminalList[0], message )
            else send( outgoingTerminalList[1], message )
        }
    }

0.16.4. Output

@category output

The output category is similar in behavior to the analysis category. It defines a base template for Components that react to incoming messages. It defines a single InputTerminal, named inputTerminal, which receives messages which should be outputted. Just like the analytics category, this category implements the onMessage handler, which shouldn't be overwritten by the Component script. Instead, it defines a new handler, output, which is called containing only the Terminal Message.

Overridable handlers defined by the output category:

    output { message ->
        log.info("Outputting the contents of $message here.")
    }

0.16.5. Runners

@category runners

The runners category defines a base template for Components that run some sort of test, for instance a request of some sort. It defines a single InputTerminal, named triggerTerminal, which is used to trigger the runner to run once. It also defines two OutputTerminals, named resultTerminal, and runningTerminal, the first of which outputs the result of each finished request, and the second which outputs the number of currently running requests, since more than one may be run in parallel. The runner category also defines several Properties. This category implements the onMessage, onConnect, and onDisconnect handlers, which should not be overridden by the Component script. It also defines two new handlers, sample and onCancel, which need to be implemented by the script. The sample handler should implement the request, and should return with the resulting Terminal Message which should be sent from the results Terminal. If a request is cancelled during execution, a SampleCancelledException should be thrown. The sample handler is called with two arguments, the triggering Terminal Message and a sample ID. The sample ID object can be ignored unless the request is done asynchronously. the onCancel handler should cancel any currently running requests.

A request may be run asynchronously, in which case the sample handler can't return the result, since it is not yet available. In this case, the handler should return a null value to notify that the request is in progress, but has not completed. Once the request has completed, the sampleCompleted-method must be called with two arguments: The result Terminal message, and the original sample ID which was passed to the sample handler.

Pre-defined Properties:

  • concurrentSamples - Long with a default value of 100. Defines how many requests to run in parallell at max.
  • maxQueueSize - Long with a default value of 1000. Defines how many requests to queue before dropping requests, when the maximum number of concurrent requests has been reached.
  • assertOnOverflow - Boolean with a default value of false. When true causes an assertion failure to be generated when a request is dropped due to the queue being full.

Overridable handler defined by the runner category:

    sample = { message, sampleId ->
        try {
            def response = doSomeRequest()
        } catch ( e ) {
            throw new SampleCancelledException()
        }
        message['Response'] = response
        message['Bytes'] = response.length()

        return message
    }

    onCancel = {
        cancelMyRunningRequests()
    }

0.16.6. Scheduler

@category scheduler

The scheduler category defines a base template for Components which are used to schedule the execution of certain other Components or TestCases. It defines a single OutputTerminal, named outgoingTerminal, which is used to control another Components execution, by sending out enable/disable messages. It also defines an InputTerminal, named stateTerminal, which is used to schedule the scheduler itself, using another scheduler. This category implements the onMessage handler, which takes care of listening for enable/disable messages, and it defines a Boolean Property named stateProperty, which gives the current state of the Component. Enabled if true, disabled if false.

The category also defines a method which should be used to send the enable/disable message to connected Components, sendEnabled( boolean state ), which should be used to either enable or disable attached Components. If the Scheduler Components state Property is false, this method will instantly return, without sending any Terminal Message.

Pre-defined Properties:

  • stateProperty - Boolean with a default value of true. Defines the enabled or on state of the Component. This Property is automatically exposed to the user via the GUI, and can be controlled by sending an enable/disable message to the stateTerminal InputTerminal of the Component.

0.16.7. Generators

@category generators

The generators category is used to generate load for other Components (mainly runners). This is done by sending trigger messages, which trigger other Components to act. The category defines an OutputTerminal, named triggerTerminal, which is used to send these messages. This is done by invoking the trigger() method. The trigger() method will only send a trigger message if the stateProperty Property is set to enabled (true). Generators are schedulable, and thus have a stateProperty Property, which can be controlled either via a Scheduler, or by user interaction through the GUI. This category implements the onMessage handler, which takes care of listening for enable/disable messages.

Pre-defined Properties:

  • stateProperty - Boolean with a default value of true. Defines the enabled or on state of the Component. This Property is automatically exposed to the user via the GUI, and can be controlled by sending an enable/disable message to the stateTerminal InputTerminal of the Component.

0.17. Custom Statistics

Since LoadUI 1.5 it is possible to add custom Statistics to Components. This is done by adding a Statistic Variable to a Component, assigning one or more Statistic Writers to it, and periodically updating the Variable with new data.

0.18. Creating a Statistic Variable

Creating a Statistic Variable is done using the addStatisticVariable() method, which takes a name for the Statistic Variable as the first argument, description as second, and then one or more names of Statistic Writers to assign to it. Once created, the Statistic Variable is updated using the update() method, which takes teo arguments: the timestamp for the numerical value being entered, and the value itself.

For example:

timeTaken = addStatisticVariable( "TimeTaken", "The time a request takes", "SAMPLE", "MINMAX" )

...

//For each completed request:
timeTaken.update( requestTimestamp, requestTime )

0.19. Statistic Writers

The following Statistic Writers are included in LoadUI.

0.19.1. MINMAX

The MINMAX Statistic Writer looks at the highest and lowest values that the Statistic Variable has been updated with in the current calculation period (the time between two points in the chart).

Exposed statistics:

  • MIN - The lowest value for the Statistic Variable.
  • MAX - The highest value for the Statistic Variable.

0.19.2. SAMPLE

The SAMPLE Statistic Writer treats each update value as a sample of a larger population, and calculates a number of statistical values from it, based on all the values in the current calculation period.

Exposed statistics:

  • AVERAGE - The average of all the values for the Statistic Variable.
  • MEDIAN - The median value of all the values for the Statistic Variable.
  • PERCENTILE_90TH - The 90th percentile of the values for the Statistic Variable. 90% of all values are below this value, 10% are above it.
  • PERCENTILE_75TH - The 75th percentile of the values for the Statistic Variable. 75% of all values are below this value, 25% are above it.
  • PERCENTILE_25TH - The 25th percentile of the values for the Statistic Variable. 25% of all values are below this value, 75% are above it.
  • STD_DEV - The standard deviation of the valuues for the Statistic Variable.
  • MIN - The lowest value for the Statistic Variable.
  • MAX - The highest value for the Statistic Variable.

0.19.3. THROUGHPUT

The THROUGHPUT Statistic Writer treats each update value as a response size of a completed request.

Exposed statistics:

  • TPS - Transactions per second, based on the number of times update was called in the last calculation period.
  • BPS - Bytes per second, based on the values of the calls to update.

0.19.4. VARIABLE

The VARIABLE Statistic Writer treats each update value as a change in a variable. More updated here just means that the variable is changing more often. After a call to update, the variable is assumed to retain its value until the next call to update.

Exposed statistics:

  • VALUE - The average value that the variable has taken during the last calculation period. The values are weighted with the time that each value has been set.

0.19.5. COUNTER

The COUNTER Statistic Writer adapts a Counter to a Statistic Variable. When using this Statistic Writer, the CounterStatisticSupport helper class should be used to handle updating, instead of manually doing so.

Exposed statistics:

  • TOTAL - Shows the latest read value of the Counter, which is monotonically increasing.
  • PER_SECOND - Shows the rate of growth in the Counter, on a unit-per-second basis.

0.20. Component Layout

Components uses the MiG Layout for laying out its contents, which you can read more about here. The layout is defined using the layout, and compactLayout methods, which are identical except that one defines the normal layout and the other defines the layout used in compact mode. The layout allows the user to interact with the component as well as see what it is doing.

0.20.1. Containers

The layout method creates a root container, but you can also create sub-containers. Each container contains child nodes, and is responsible for laying them out within itself. They can be controller using the named parameters layout, column, row, and constraints, which correspond to MiG Layout Layout Constraints, Column Constraints, Row Constraints, and Component Constraints. Creating an additional container is done by using the box method.

Examples:

    //The root container containing one sub-container, no constraints:
    layout {
        box {
        }
    }

    //The root container containing multiple sub-containers, with and without constraint:
    layout(layout:'wrap 2, ins 0') {
        box(layout: 'wrap', col: 'growx') {
        }
        box {
        }
    }

There is a special display container used for creating the LCD display look which many of the Components use. This is achieved by using the box method with a specific widget parameter:

    layout {
        box(widget: 'display') {
        }
    }

0.20.2. Controls

Each layout control can take a constraints parameter, just like containers, as well as additional parameters depending on the control type. The available layout control are:

0.20.2.1. property

Exposes a Property to the user, allowing the user to change its value. The widget used to represent the Property depends on the type of the Property, as well as the other parameters given. Parameters:

  • property - Required, the Property to expose.
  • label - A label to be displayed next to the Property.
  • options - A list of valid values for the Property.
  • min - The minimum value allowed for the Property (numeric Properties only).
  • max - The maximum value allowed for the Property (numeric Properties only).
  • step - The smallest increment step for a Property (numeric Properties only).

Examples:

    layout {
        property( property: myNumber, label: 'My Number', min: 0, max: 100 )
        property( property: myNumber2, label: 'Other Number', min: 0, step: 10, constraints: 'spanx 2' )
    }

0.20.2.2. node

Typically a text node, but can also be used for arbitrary JavaFX Nodes. The most common usage for this is to display status of the Component in displays. Parameters:

  • label - A label to be displayed.
  • content - The value to be displayed. This can be any JavaFX Node. If this is a closure (or a Value, such as a Counter or Property), it will be periodically re-evaluated to display the current value.

Example:

    layout {
        node( label: 'Completed Requests', content: { getRequestCount() } )
    }

0.20.2.3. label

Used to display a text.

Example:

    layout {
        label( 'Please enter the required value below.' )
    }

0.20.2.4. action

Allows the user to trigger an action. Parameters:

  • label - A label to be displayed for the action.
  • action - A closure to be invoked when the user initiates the action.
  • enabled - A boolean value to determine of the action should be enabled or not. This can at any time be changed by storing the return value of the action component and setting its enabled-field.

Examples:

    layout {
        action( label: 'Do Something', action { log.info('Doing stuff!') } )
        badAction = action( label: 'Do Not Press', action: { log.info('OH NO!') }, enabled: false )
    }

    //Elsewhere...
    badAction.enabled = true

0.20.2.5. separator

A horizontal or vertical separator, used to group components in logical segments. Parameters:

  • vertical - A boolean value to determine if the separator should be vertical (true) or horizontal (false). It defaults to horizontal.

0.21. Settings

Components can have tabs in the settings dialog for it. These are created much like the way the layout is specified, but using the settings method, which takes a label parameter for the tab name, as well as a closure defining the Properties to show. The constraint parameters used by MiG Layout are not applicable here. Unlike layout, the settings method can be called multiple times, once for each tab to create.

Examples:

    settings( label: 'Basic' ) {
        property( property: readResponse, label: 'Read Response' )
    }

    settings( label: 'Authentication' ) {
        label( 'Note that passwords are case-sensitive.' )
        property( property: usernameProperty, label: 'Username' )
        property( property: passwordProperty, label: 'Password', widget: 'password' )
    }

0.22. Utilities

Besides what has already been listed, there are several utility methods to aide you in performing tasks we've found to be commonly used when creating Components.

0.22.1. Logging

Each Component has access to a log object, which can be used for debugging purposes. This is a standard SLF4J Logger object, and can be used as such. For example:

    log.info( 'Logs a message' )

    try {
        ...
    } catch( e ) {
        log.error( 'An exception occurred:', e )
    }

We also provide a mechanism for logging messages that are relevant to the current test execution. These differ from the regular log, in that they are also displayed in the Test Event log in the Statistics Workbench. This also means that they are saved as part of the execution, and can be viewed lated when looking at an old result. There are two severity levels for this type of message: notify and warn. Each of these take a message, and optionally a timestamp for when the action resulting in the message occurred (defaults to the current time). For example:

    notify( 'Connection was made' )

    warn( 'Connection was lost', timestamp ) //Logs a warning using the given timestamp.

0.22.2. Scheduling tasks

It's pretty common for Components to schedule some task to be run at a specific part of the test execution lifecycle, or to run periodically, and we provide some methods to help with this.

0.22.2.1. Executor

Each Component has access to a standard Java ScheduledExecutorService. The framework takes care of things like cancelling tasks if the Component is deleted or changed, so for the most part you can just schedule and forget. The following methods are available to use directly:

  • submit - The same as ScheduledExecutorService.submit().
  • schedule - The same as ScheduledExecutorService.schedule().
  • scheduleWithFixedDelay - The same as ScheduledExecutorService.scheduleWithFixedDelay().
  • scheduleAtFixedRate - The same as ScheduledExecutorService.scheduleAtFixedRate().
  • cancelTasks - Cancels all scheduled tasks.

For example:

    scheduleAtFixedRate( { log.info( 'Tick!' ) }, 0, 1, TimeUnit.SECONDS )

0.22.2.2. Test Execution Phases

You can also schedule tasks to run as specific phases of the test execution. This is done using the duringPhase method, which takes a Phase and a closure to execute. Only one task can be scheduled for each Phase. Calling the duringPhase method multiple times for the same Phase will only cause the last task registered for the Phase to execute. The duringPhase method can also be called without a closure to cancel the currently registered task for the Phase.

For example:

    duringPhase( Phase.START ) {
        log.info( 'Starting up!' )
    }

    duringPhase( 'STOP' ) { //You can either pass in a Phase value, or a valid name of a Phase.
        log.info( 'Stopping!' )
    }

0.22.3. Aggregating distributed values

It's pretty common for components to keep track of some numerical value (number of requests sent, current items in queue, etc.) which is different for each Agent, but should be displayed to the user as the sum of all distributed values. To simplify this, LoadUI provides a total method, which takes care of sending the agent values back and summing the result on the controller. The total method is called with a name to give the sum variable, as well as a closure used to calculate the current local value. The sum variable can be used in the layout section of the Component to show the total value of all agents involved. For example:

    def currentlyQueued = 0 //Local variable which may change often
    total( 'queueSizeTotal' ) { queueSize - currentlyQueued } //Calculates the local queue size

    // ... 

    layout {
        node( label: 'Queued', content: queueSizeTotal ) //Will display the total among all agents
    }