In the past few years I’ve seen an increasing interest in Flex applications at our customers. I have to say that I’m not surprised about this trend. Not only do Flex applications generally look great, but they also provide a big boost to user experience. As a developer and architect I am also quite pleased with the programming model and extensive widget library. Sure, Adobe can still improve on a lot of things, but so far I have always worked with pleasure on Flex applications.

Recently, at one of our customers, I bumped into another interesting side of Flex application development, namely performance testing (e.g. load-testing or stress-testing). Surprisingly enough, it is very hard to find an affordable solution for this. There are commercial tools out there that seem to do it quite well (Neoload) or reasonably well (WebLOAD) but I could not find a single viable open source alternative.

Of course, it’s not exactly straightforward to write such a tool. The difficulty lies in the ability to generate and send AMF requests to the webserver, plus the interpretation of the AMF response you get back. The standard approach that most performance testing tools use is based on the assumption of HTTP requests and responses. While the AMF protocol technically “hitches a ride” on the HTTP protocol as well, the content is packed into a binary format (AMF) that can’t be interpreted the regular way HTTP requests and responses can. Hence the need for a dedicated tool that can interpret AMF. However, not every customer has tens of thousands of dollars to spend. So, how do you cope with this?

AMF request generation

As it turns out, with a little ingenuity and the help of the BlazeDS codebase, you can create a solution that together with the excellent JMeter allows you to execute performance tests just as you are used to doing. Let’s start with the generation of AMF requests.

In the blazeds-core.jar there are representations of AMF messages and ways of de-/serializing them. This is actually used by BlazeDS itself to convert between AMF data to a Java Object model and vice versa. Because they kindly open sourced this code, I was able to reuse these classes to generate AMF requests and save those to a file. Here are a couple of code snippets I wrote to do this:

public byte[] generate(AmfRequestParameters params) {
    ActionMessage message = new ActionMessage();
    message.addBody(createMessageBody(params));

    ByteArrayOutputStream out = new ByteArrayOutputStream();
    MessageSerializer serializer = createMessageSerializer(out);
    try {
        serializer.writeMessage(message);
    } catch (IOException e) {
        throw new AmfRequestGenerationException(e);
    }

    return out.toByteArray();
}

First thing to note is the creation of an ActionMessage. This is actually a representation of an AMF request to which we will add a body in the second line of this method. Because we want to save this AMF request to a file we of course need to serialize it first. We do this by using the MessageSerializer class BlazeDS kindly provides. We let it serialize the message to a ByteArrayOutputStream so that we can potentially use it for other purposes than saving it to a File, but you could just as easily pass in a FileOutputStream. Finally, we return the byte array back to the client that called this method.

Building the message

So, let’s have a look at the createMessageBody(params) method:

protected MessageBody createMessageBody(AmfRequestParameters params) {
    RemotingMessage message = new RemotingMessage();
    message.setHeader(HEADER_DS_ENDPOINT, channel);
    message.setHeader(HEADER_DS_ID, "1");
    message.setMessageId("1");
    message.setDestination(params.getDestination());
    message.setOperation(params.getOperation());
    message.setBody(params.getParameters());

    return new MessageBody(null, null, new RemotingMessage[] {message});
}

What we do in this method is create an instance of a RemoteMessage. If you have ever inspected the contents of an AMF request, some of the properties might be familiar to you. Two headers are set: “DSEndpoint” and “DSId”. The endpoint is actually the channel in your services-config.xml file that you use to send all communication through (by default called something like ‘channel-amf’). I am not quite sure what the id header is supposed to do, but BlazeDS seems to require it. However the value does not seem to matter, hence the “1”. Neither does the message id for that matter.

The interesting parts are the destination, operation and parameters. These properties relate to the backend service you want to call, the method on that service and any arguments for that method. Finally, we return a MessageBody with this RemotingMessage that will be added to the ActionMessage in the generate() method which we saw earlier.

I introduced a small data holder class to hold the dynamic values of the message, called AmfRequestParameters:

public class AmfRequestParameters {

    private String destination;
    private String operation;
    private Object[] parameters;

    /**
     * Creates a new {@code AmfRequestParameters} with the given fields.
     *
     * @param destination destination of the amf request
     * @param operation operation to call on the destination
     * @param parameters parameters of the operation
     */
    public AmfRequestParameters(String destination, String operation, Object[] parameters) {
        this.destination = destination;
        this.operation = operation;
        this.parameters = parameters;
    }

    // getters and setters omitted
}

Creating a message serializer

The last thing that needs to be done is serializing the message. This can be done with a MessageSerializer:

protected MessageSerializer createMessageSerializer(OutputStream out) {
    SerializationContext context = new SerializationContext();
    context.setSerializerClass(AmfMessageSerializer.class);
    SerializationContext.setSerializationContext(context);

    MessageSerializer serializer = context.newMessageSerializer();
    serializer.initialize(context, out, null);

    return serializer;
}

In order to create a MessageSerializer, BlazeDS needs to know what type of serializer it needs to create. This is done via a SerializationContext on which you specify the AmfMessageSerializer class as the one it should instantiate. We then initialize the serializer with the context and the chosen outputstream, which is a ByteArrayOutputStream in our case. Optionally, you can add an AmfTrace to the initialization procedure, but I didn’t find that to be of any value to me.

Adding the AMF request to JMeter

In the end, we now have a byte array that contains a serialized AMF message. This can easily be stored to a file, after which it can be loaded into JMeter:

JMeter AMF request screenshot

A couple of things are important to know when sending AMF requests with JMeter. First of all you need to make sure that you point JMeter to the URL that exposes your backend services to your Flex application. This is actually the endpoint URI under the channel element in your services-config.xml file. Second of all, make sure that you specify the HTTP request method as POST. Makes sense if we’re sending a file along with the request. Finally, point JMeter to the AMF request file to include in the request and specify its MIME type as “application/x-amf”.

And that’s it, you are all set for performance testing your Flex BlazeDS application!

Limitations

There are a couple of limitations to this approach:

  • You can’t inspect the response of a request in detail. As the response is also in AMF, JMeter can’t inspect it and make assertions on it
  • HTTP response codes are less meaningful. As basically every response from the server is a HTTP 200 response code, we can’t detect well if an error was thrown. For example, if we get a BadCredentialsException during login, we could have added an assertion to JMeter to check if the user actually got to the next screen, instead of being redirected to the login screen. Unfortunately, this does not work for Flex applications as the client application itself has to make a decision on what do with this type of error (instead of the server doing this for us)
  • This approach is limited to BlazeDS only. Due to the code we’re using from the BlazeDS codebase, we can’t for example use WebORB instead of BlazeDS. This is mostly due to the fact that there are subtle differences in the way certain data types are serialized and deserialized
  • Maybe the biggest drawback is that you have to create an AMF request file for each variation on a request. For example, if we want to login with 100 different users, we also need to create 100 login request files. Normally, you would be able to script this in JMeter by adding variables to the request parameters.

Currently I am looking into solving some of these limitations by making use of JMeter’s PreProcessors and PostProcessors. You can add custom Java code to JMeter that will execute on each request. You may therefore be able to generate the AMF request file on the fly.

Conclusion

By making use of the BlazeDS codebase and JMeter we are able to performance test a Flex BlazeDS application in a very cost-effective manner. JMeter has proven itself over the years to be an excellent tool for performance testing and it is a relief that, with a little ingenuity, we can use it for Flex applications too.

I think I’ve only scratched the surface on what can be accomplished with this combination. There is definetely a lot of potential to be explored here and I encourage everyone that has read this blog entry to go out there and try it yourself!