MIDI control your loadUI tests

For our next loadUI component we are going to do something totally different, zany and fun! We're going to dig out that old MIDI controller you have buried in your closet or cellar (admit it...) and turn it into a load-testing control-panel, giving you possibilities to do real exploratory load testing at the tweak of a knob, literally. So put on your body-socks and pretend you are the fifth member of Kraftwerk, let's go!

Background


Obviously we were pretty inspired by music software when we created loadUI, and also very early on we joked about the possibility to actually control loadUI components and tests by tweaking real buttons. The idea came up again before last Christmas, and this time we decided to give it a shot. The MIDI APIs in Java seemed to cover our needs just fine, and the rest of the component wouldn't have to be too complicated; it basically would be used to map incoming MIDI control-change messages to component properties in your loadUI test setup, for example the rate of a generator or delay of a delay component.

The midiKontrol component


The component was straightforward to implement, and just about 400 lines of groovy code later we had a working version in loadUI:

It doesn't look like much, but hooking it up with our MIDI controller (a Korg nanoKontrol) we immediately could start controlling our loadUI components and the test execution itself;

  • Select the nanoKontrol (or whatever MIDI input device you may be using) in the source box
  • Select the target component and property in the middle
  • Select the desired controller ID with the knob at top left


Since this last step might not be that easy, we added an output terminal that simply logs all incoming MIDI messages so we can use a TableLog to see what is going on:

 

midiKontrol configured


Twiddling the knobs on our controller shows which controller ID to specify for each knob, and after a while we were all set. Also in the settings panel you can specify control messages to use as start/stop actions, and how to handle min/max values:

 

midiKontrol settings

Since the nanoKontrol has start/stop buttons this was a perfect match, and the final setup lets us both start and stop the loadUI test and adjust the load as desired while testing:

DSC 0733

We can add as many midiKontrol components as we want, each configured to use a different input controller (or the same) and map them to different properties in our loadUI tests, including properties inside testcases that might be distributed to loadUI agents, etc. Here I've added a soapUI Runner controlled by a Fixed Rate generator which I've hooked up to an older MIDI device (which IMHO looks way cooler):

DSC 0736

(If you want to see the component in action head straight over to the live recording of our latest Q&A where we demo:ed this in all its glory)

The Business Case?


The initial take on this component might be that it's just for playing around and not any "serious" testing. Actually we don't agree!

  1. Exploratory Load Testing is neccessary - just like a road test for a car shows its capabilities in real-life situations, an exploratory load test where you continously adjust the load to simulate different types of system and client behaviours gives you the same benefit and understanding of how your system would perform in real-life situations. And wiring the rate of your 8 generators in your different testcases (some distributed) to one controller makes it so much easier to "play around" with the generated loadUI to see how the target services handle this.
  2. Give it to your manager - just like the Form editor in soapUI Pro makes the ad-hoc testing of system interfaces accessible to non-technical stakeholders and managers, adding a physical interface for project stakeholders to play with gives them a new possibility to actually take part of the project and get a feel for it (and have some fun).
  3. Having fun is good for business - self explanatory - just look at us at eviware ;-)

The component itself


You can download the component from the bottom of this page, lets just have a quick peek at some key points of the implementation. Most of the code is "standard loadUI component stuff" that has been covered in my previous blog posts, what is different here is of course the MIDI handling itself; we need to implement the Receiver interface for receiving MIDI messages and hook that up with the available devices in the system ("Transmitters" in Java MIDI speak);

// init MIDI in options
transmitterNames = []

for( info in MidiSystem.midiDeviceInfo )
{
if( MidiSystem.getMidiDevice( info )?.maxTransmitters != 0 )
transmitterNames += info.name
}

transmitterOptions.options = transmitterNames

...

def midiReceiver = new MidiReceiver( context, midiLog, executor )

def initMidiReceiver =
{
// midiDevice?.close()
midiTransmitter?.close()

for( info in MidiSystem.midiDeviceInfo )
{
if( info.name == transmitter?.value )
{
System.out.println( "Connecting to MIDI Device $info.name" )
midiDevice = MidiSystem.getMidiDevice(info )
if( !midiDevice.isOpen() )
midiDevice.open()

midiTransmitter = midiDevice.transmitter
midiTransmitter.receiver = midiReceiver
break;
}
}
}


The main event-handler in the MidiReceiver class is straightforward; check the incoming message to see if it is a control message and dispatch it accordingly. If the controller ID matches the configured controller in the component, just update the value;

void send( MidiMessage message, long timeStamp )
{
try
{
// right type of message?
if( message instanceof ShortMessage )
{
// control change message?
if( message.command == 176 )
{
// our configured control?
if(message.data1 == ctxt.controller.value )
{
// update the configured target property
ctxt.value.value = message.data2
}
...


which will be picked up by the corresponding event-handler, which will update the target component as well:

updateTargetProperty =
{
def p = getTargetProperty()
p?.value = value.value * scale.value
}

addEventListener( PropertyEvent ) { event ->
...
else if( event.property == value )
{
updateTargetProperty()
}
}


Events for starting and stopping the loadUI tests are configurable in the settings dialog as shown above, the code being;

...
// button pressed?
else if( message.data2 == 127 )
{
// configured start button?
if( message.data1 == ctxt.startCtrl.value )
{
if( ctxt.canvas.running )
ctxt.canvas.triggerAction( "STOP" )
else
ctxt.canvas.triggerAction( "START" )
}
// configured stop button?
else if(message.data1 == ctxt.stopCtrl.value )
{
def running = ctxt.canvas.running
ctxt.canvas.triggerAction( "STOP" )
if(!running)
ctxt.canvas.triggerAction( "COMPLETE" )
}


Some effort was put into allowing you to configure up/down buttons on some controllers to increase/decrease values continuously. For buttons the controller sends 127 as the data when the button is pressed, and 0 when it is released. The component handles this with background tasks that are triggered when the corresponding 127 and 0 values are received for the configured MIDI controller.

...
// configured controller -> continuous increase
else if(message.data1 == ctxt.incCtrl.value )
{
ctxt.value.value = ctxt.value.value+ctxt.ctrlStep.value

if( incFuture != null )
incFuture.cancel( false )

incFuture = executor.scheduleWithFixedDelay(
{
ctxt.value.value = ctxt.value.value+ctxt.ctrlStep.value
}, 500, 100, TimeUnit.MILLISECONDS )
}
// configured controller -> continuous decrease
else if(message.data1 == ctxt.decCtrl.value )
{
if( ctxt.value.value >= ctxt.ctrlStep.value )
{
ctxt.value.value = ctxt.value.value-ctxt.ctrlStep.value
if( decFuture != null )
decFuture.cancel( false )

decFuture = executor.scheduleWithFixedDelay(
{
if( ctxt.value.value >= ctxt.ctrlStep.value )
{
ctxt.value.value = ctxt.value.value-ctxt.ctrlStep.value
}
}, 500, 100, TimeUnit.MILLISECONDS )
}
}
}
// button released?
else if( message.data2 == 0 )
{
// stop continuous increase if running
if(message.data1 == ctxt.incCtrl.value && incFuture != null )
{
incFuture.cancel( false )
incFuture = null
}
// stop continuous decrease if running
else if(message.data1 == ctxt.decCtrl.value && decFuture != null )
{
decFuture.cancel( false )
decFuture = null
}
}
}


Finally the component sends all incoming messages to the defined output terminal, both for logging purposes and for giving you the possibility to create other components that react to MIDI events without having to implement the actual MIDI controller handling; just create a component with an input that you connect to this output and process the incoming MIDI messages as desired. Maybe hook them up to the awesome JSyn library for actually generating sound from your load tests!?

// ignore sysex messages
if( message.command != 240 )
{
// output message
def logMessage = ctxt.context.newMessage()
logMessage["Channel"] = message.channel
logMessage["Command"] = message.command
logMessage["Data1"] = message.data1
logMessage["Data2"] = message.data2
ctxt.context.send( output, logMessage )
}


That's it! Download the component from here, unzip it into your loadUI script-components folder, connect that old and dusty (or new and shiny) MIDI controller, and off you go to load-testing fame! Also make sure you are using the latest loadUI version, preferably the nightly build which contains all the latest fixes and improvements. And if you want to read more about the Java MIDI APIs, look no further.

And please... if you have a cool setup, send us a photo and we'll publish it here for all to admire.

All the best!

/Ole


Close

Add a little SmartBear to your life

Stay on top of your Software game with the latest developer tips, best practices and news, delivered straight to your inbox

By submitting this form, you agree to our
Terms of Use and Privacy Policy

Thanks for Subscribing

Keep an eye on your inbox for more great content.

Continue Reading