[[loading-testing-with-gatling]]
Load testing with Gatling
-------------------------
http://gatling.io[Gatling] is a powerful tool for load testing. Compared
to WebDriver/Selenium/TestBench, it doesn't render the actual content,
but just simulates messages clients send to the server. The server
doesn't know if the test tool actually does something with the
responses, so it gives you perfectly valid numbers for your applications
performance - on the server side. It scales very well, so you don' t
need huge army of nodes to bombard your application under test. It can
be used with Vaadin as such, but with these tips you hopefully get
started easier.
[[vaadin-tips-to-make-tests-more-stable-and-easier-to-create]]
Vaadin tips to make tests more stable and easier to create
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Gatling works with Vaadin "out of the box", but there are some obstacles
you might face if you don't understand a bit how Vaadin communication
works. E.g. plain recordings from standard Vaadin apps will not work
properly.
The communication that Vaadin's "thin client" in browser does with the
server side has couple of checks that it does to improve robustness and
security. One can simulate these with Gatling as well, by e.g.
https://github.com/mstahv/v-quiz/blob/master/src/test/scala/loadtest/WebSocketVaadinSimulation.scala#L84[reading the XSRF preventation key into a variable] and passing the value
https://github.com/mstahv/v-quiz/blob/master/src/test/scala/loadtest/WebSocketVaadinSimulation.scala#L95[in
each response]. However, these setting can be disabled during load
testing to make it easier to write and maintain your application. The
effect for the scalability, when disabling or configuring these, should
be negligible. Feel free to do these, but also remember to remove these
"hacks" when building your production war file. Consider e.g. using
separate maven profile and inject different parameters with it.
[[disabling-xsrf-presentation-key]]
Disabling XSRF presentation key
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The XSRF preventation can be disabled with following servlet parameter
(or similar servlet 3 style parameter). NOTE, do not leave this for
public apps in production.
[source,xml]
....
disable-xsrf-protection
true
....
[[disabling-syncid-happens-with-similar-parameter]]
Disabling syncId happens with similar parameter
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[source,xml]
....
syncIdCheck
false
....
If you want to do the above with Java Servlet 3.0 annotations, use the
following:
[source,java]
....
initParams = {
@WebInitParam(name = "disable-xsrf-protection", value = "true"),
@WebInitParam(name = "syncIdCheck", value = "false")}
....
[[using-debug-ids-in-communication]]
Using debug ids in communication
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you want to minimize the effort needed for maintaining your
scalability tests, you probably want to do a small hack to the Vaadin
communication mechanism. Normally Vaadin uses a incrementing session
wide identifier to connect components to their "client side
counterparts". Thus, if you add a one single component to your login
screen, the whole load test simulation might be broken.
You can set "id" for each component, but in recent Vaadin versions this
id is no more used in the communication, but only assigned to
client dom. This can still be enforced with a specially crafted
extension to VaadinServlet. An implementation for the "competing tool" JMeter can be
found at <>. This implementation works for Gatling users
as well. Note that, it is suggested to do this only for load testing, and NOT
for the production.
[[ignoring-obsolete-static-file-requests]]
Ignoring "obsolete" static file requests
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
One of the most simplest and cheapest method to improve your apps
scalability is to serve static files form a separate server or from a
CDN provider. Thus it might make sense to leave loading those files away
from your test script. If you do the script manually, just don't add
requests for static files (js/css/images/...). If you recorded you test
case, just remove these form the script. Check out the example project
that only uses the required files.
[[testing-with-websockets]]
Testing with WebSockets
~~~~~~~~~~~~~~~~~~~~~~~
If your want to load test your application with the most advanced
communication channel, WebSockets, you can do that with Gatling as well.
Using the recorder in this case doesn't work, but handcrafting the test
case isn't that hard once you get started. The example app has a branch
with WebSocket test case. With WebSocket communication it might also be
handy to disable xsrf preventation and the so called "syncid".
First two request are just normal http requests. The first gets the
"host page" and also the initial state request is done with normal XHR.
The difference to normal Vaadin communication is that it is to be sent
to "/PUSH" address.
After the initial state request you start to use the special WebSocket
API in Gatling. There are lot of things to keep in mind with this
fundamentally totally different kind of communication mechanism. Check
out Gatling's generic websocket help for basic info.
When you start handcrafting the WebSocket simulation, the easiest tool
is probably Chrome's dev tools. With that you can open normal browser
session and "sniff" the traffic that is sent to the server and also the
messages that are received. An easy option is just to copy paste the
payloads and possibly add some verification to ensure proper answers are
received. The websocket example is built with special variable to work
without disabling xsrf verification.
If you are using random input in your load tests, something that is
highly suggested for realistic numbers, you might end up in small
problems. The message format, by Atmosphere, has a weird number and "|"
in front of each message. That number tells the message length and it
must really match the real message length. Create a simple helper
function to calculate that if your input data length varies.
[source,javascript]
....
import io.gatling.core.session._
import io.gatling.core.session.el._
def atmoMessage(message: Expression[String]) = message.map(m => m.length + '|' + m)
.sendText(atmoMessage("SomeMessage"))
....
If (and when) you probably want to close the websocket connection
cleanly, you need to notify the server with an extra xhr with a
identifier given by the atmosphere framework. The key is the first
message that the server sends when you connect to it.
Check out this script for
https://github.com/mstahv/v-quiz/blob/master/src/test/scala/loadtest/WebSocketVaadinSimulation.scala[an
example using WebSocket] communication. It also saves XSRF preventation
key to variable, so it don't need it to be disabled from the server.
[[configuring-gatling-to-the-web-app-build]]
Configuring Gatling to the Web app build
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is a good habit to keep your tests in the same projects as your
actual application. Then you can easily verify your application still
scales, after you have for example written a cryptic SQL query.
Even better if you can make a Gatling script to be executed during
builds to make. Gatling has http://gatling.io/docs/current/extensions/maven_plugin/[a
maven plugin] that can do exactly this thing.
https://github.com/mstahv/gatling-vaadin-example[The example project
setup] executes a test during basic "mvn install". With similar setup in
a real project, your CI server most likely saves results stored under
target directory. This way it is easy to check it out afterwards how the
performance of your application has evolved during its development.
[[jmeter-vaadin-servlet-extension]]
JMeter Vaadin Servlet extension
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The implementation referred to in <>
[source,java]
....
package com.example.vaadin7jmeterservlet;
import com.vaadin.server.ClientConnector;
import com.vaadin.server.DeploymentConfiguration;
import com.vaadin.server.ServiceException;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinService;
import com.vaadin.server.VaadinServlet;
import com.vaadin.server.VaadinServletService;
import com.vaadin.server.VaadinSession;
import com.vaadin.ui.Component;
/**
* @author Marcus Hellberg (marcus@vaadin.com)
* Further modified by Johannes Tuikkala (johannes@vaadin.com)
*/
public class JMeterServlet extends VaadinServlet {
private static final long serialVersionUID = 898354532369443197L;
public JMeterServlet() {
System.setProperty(getPackageName() + "." + "disable-xsrf-protection",
"true");
}
@Override
protected VaadinServletService createServletService(
DeploymentConfiguration deploymentConfiguration)
throws ServiceException {
JMeterService service = new JMeterService(this, deploymentConfiguration);
service.init();
return service;
}
private String getPackageName() {
String pkgName;
final Package pkg = this.getClass().getPackage();
if (pkg != null) {
pkgName = pkg.getName();
} else {
final String className = this.getClass().getName();
pkgName = new String(className.toCharArray(), 0,
className.lastIndexOf('.'));
}
return pkgName;
}
public static class JMeterService extends VaadinServletService {
private static final long serialVersionUID = -5874716650679865909L;
public JMeterService(VaadinServlet servlet,
DeploymentConfiguration deploymentConfiguration)
throws ServiceException {
super(servlet, deploymentConfiguration);
}
@Override
protected VaadinSession createVaadinSession(VaadinRequest request)
throws ServiceException {
return new JMeterSession(this);
}
}
public static class JMeterSession extends VaadinSession {
private static final long serialVersionUID = 4596901275146146127L;
public JMeterSession(VaadinService service) {
super(service);
}
@Override
public String createConnectorId(ClientConnector connector) {
if (connector instanceof Component) {
Component component = (Component) connector;
return component.getId() == null ? super
.createConnectorId(connector) : component.getId();
}
return super.createConnectorId(connector);
}
}
}
....