I’ve been working on a pet project of my own lately, which is an Akka Streams wrapper for ZeroMQ sockets. In order to test the library and the target application itself, I wanted to build some load tests, which could determine how well they performed under an arbitrary pressure.
There are several ways one could approach this, but if you want to stick to an existing framework and work on a Scala project, than Gatling is probably a good choice. The framework provides an elegant DSL and can be integrated with an sbt project using the official plugin. You can find a brief summary of the core features on the project’s home page and in this blog post by ThoughtWorks.
Gatling is protocol-agnostic at its core, which allows to build some custom testing scenarios on top of it. However, most examples are focused on HTTP or JMS at the moment, which makes it somehow difficult to begin with.
This article is a summary of my own process with a custom DSL implementation for ZeroMQ. I hope you’ll find it useful, if you’re implementing a protocol of your own.
Big picture
The initial test case I took was a simple publisher-subscriber fan-out, which would occur over relevant ZeroMQ sockets. My Gatling simulation would look something like this:
As you’ve probably figured out, the idea is to create a group of simultaneous subscribers, that will listen to some fixed topic over TCP/IP and read an arbitrary number of messages.
The publisher is assumed to be working on the given address. It’s implementation will be skipped in this article, but you can assume, that it will provide a constant stream of ascending integers.
Preparing the DSL
I started by analyzing the JMS module, which is a fairly simple protocol implementation, comparing to HTTP. I’ve also read the Gatling Protocol Breakdown by James Gregory, which I highly recommend to begin with.
NOTE: The following implementation is based on Gatling 2.2.0-M3. Several things had changed between 2.1.x and 2.2.x in the context of custom protocol implementation (in a good way). I would recommend checking the relevant GitHub branch, if you intend to work with 2.1.x.
Predef & DSL trait
The convention is to put all protocol-specific DSL into a Predef object for easy importing in your Simulation classes. Let’s start by defining it:
Protocol definition
The protocol needs a Gatling representation, which will provide shared attributes for establishing a connection:
We need to express the ZmqProtocol creation process in the DSL. The convention is to create a set of relevant builder classes, that will form a hierarchy:
Our simulation code could look like this at the moment:
You can add intermediary builders of course, if protocol creation should happen in several steps, that take some mandatory attributes on the way.
Example: the “base” builder could produce an “endpoint step” builder, which would take some mandatory parameters and create a valid ZmqProtocolBuilder in the end. The final builder is assumed to take optional configuration only. That’s why we’re creating a copy of it once an attribute is altered (both builders are immutable and can produce a valid protocol).
Action definition
An Action is a top level abstraction for executing a concrete step along a scenario. It is implemented as an Akka actor, that receives Session messages for triggering the action.
In this example we’ll implement a subscriber action, that will:
Connect to a ZMQ_PUB socket
Consume some integers from the stream
Terminate
ActionBuilder DSL
Instead of producing an Action directly, we need to create a hierarchy of DSL builders, that will be converted to an ActionBuilder instance when evaluated in the simulation class. This “action builder” will be used by Gatling to create concrete Action instances during scenario execution later.
Let’s define the DSL and the ActionBuilder for the subscriber process:
We can use this in the simulation as follows:
Action implementation
One missing piece is the SubscriberAction implementation, which is created by the SubscriberActionBuilder in the previous step. We’ll implement it next:
The most important part are the statsEngine calls, which are responsible for storing request and response stats. If everything was setup correctly, then you should be able to get a valid report after all tests were executed.
Conclusions
I hope you enjoyed the article and didn’t get discouraged by implicit conversions and longish code examples.
You can find a complete version of the DSL in the reactive-zeromq project on GitHub. Please take it with a grain of salt - it’s a small experiment of mine, not a full blown communication library.