I am trying to benchmark an application running in the Cloud with JMeter. The underlying protocol uses websockets, but there are some proprietary libraries that I need to use to make those calls. I looked at some websocket plugins for JMeter. But more than just testing I want to use the ability of JMeter to be able to make distributed load generation for my server. I would like to use my own client (written in Java) to make the actual request for the server. Is this some how possible?
1 Answer
There are different implementations of WebSocket Client API, i.e. Jetty Websocket API or Java API for WebSocket
Recently I've been investigating on how WebSockets can be tested using JMeter Load Testing Cloud Solution. Kindly see proof of concept details below. It's using JavaSamplerClient API so extension being packaged as a .jar and put under JMeter Classpath will be available as Java Request Sampler.
First of all you'll need Tyrus – open source JSR-356 implementation. Tyrus requires Java 7 so make sure that you use Java SE 7 to build and execute the extension. The most convenient way of getting dependencies is using following Apache Maven configuration file
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>blazemeter-websocket</groupId> <artifactId>blazemeter-websocket</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>javax.websocket</groupId> <artifactId>javax.websocket-client-api</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.glassfish.tyrus</groupId> <artifactId>tyrus-client</artifactId> <version>1.1</version> </dependency> <dependency> <groupId>org.glassfish.tyrus</groupId> <artifactId>tyrus-container-grizzly</artifactId> <version>1.1</version> </dependency> <dependency> <groupId>javax.json</groupId> <artifactId>javax.json-api</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.glassfish</groupId> <artifactId>javax.json</artifactId> <version>1.0.1</version> </dependency> </dependencies> </project> Invokation of target “mvn dependency:copy-dependencies” will download all required jars into /target/dependency folder. However you may with to continue with build, package, etc. maven plugins.
Source code of the extension is follows:
package com.blazemeter; import org.apache.jmeter.config.Arguments; import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient; import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext; import org.apache.jmeter.samplers.SampleResult; import org.apache.jorphan.logging.LoggingManager; import org.apache.log.Logger; import org.glassfish.tyrus.client.ClientManager; import javax.websocket.*; import java.io.IOException; import java.net.URI; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @ClientEndpoint public class BlazemeterWebsocketRequest extends AbstractJavaSamplerClient { private static String ws_uri; private static String ws_message; private static String response_message; private static CountDownLatch latch; private static final Logger log = LoggingManager.getLoggerForClass(); @Override public Arguments getDefaultParameters() { Arguments params = new Arguments(); params.addArgument("URI", "ws://echo.websocket.org"); params.addArgument("Message", "Blazemeter rocks!"); return params; } @Override public void setupTest(JavaSamplerContext context) { ws_uri = context.getParameter("URI"); ws_message = context.getParameter("Message"); } @Override public SampleResult runTest(JavaSamplerContext javaSamplerContext) { SampleResult rv = new SampleResult(); rv.sampleStart(); latch = new CountDownLatch(1); ClientManager client = ClientManager.createClient(); try { client.connectToServer(BlazemeterWebsocketRequest.class, new URI(ws_uri)); latch.await(1L, TimeUnit.SECONDS); } catch (Throwable e) { throw new RuntimeException(e); } rv.setSuccessful(true); rv.setResponseMessage(response_message); rv.setResponseCode("200"); if (response_message != null) { rv.setResponseData(response_message.getBytes()); } rv.sampleEnd(); return rv; } @OnOpen public void onOpen(Session session) { log.info("Connected ... " + session.getId()); try { session.getBasicRemote().sendText(ws_message); } catch (IOException e) { throw new RuntimeException(e); } } @OnMessage public String onMessage(String message, Session session) { log.info("Received ... " + message + " on session " + session.getId()); response_message = message; try { session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE,"")); } catch (IOException e) { e.printStackTrace(); } return response_message; } @OnClose public void onClose(Session session, CloseReason closeReason) { log.info(String.format("Session %s close because of %s", session.getId(), closeReason)); } } Hope this helps,
D.