Load Testing WebSocket Endpoints in Java

Load testing WebSocket endpoints requires specialized tools and approaches due to the persistent, bidirectional nature of WebSocket connections. Here's a comprehensive guide to load testing WebSocket endpoints using various Java tools and frameworks.

Tools and Libraries

Dependencies Setup

<properties>
<jetty.version>11.0.17</jetty.version>
<jmeter.version>5.6.2</jmeter.version>
<tcpmon.version>1.0.0</tcpmon.version>
</properties>
<dependencies>
<!-- Jetty WebSocket Client -->
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-jetty-client</artifactId>
<version>${jetty.version}</version>
</dependency>
<!-- Jetty WebSocket Server -->
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-jetty-server</artifactId>
<version>${jetty.version}</version>
</dependency>
<!-- Java WebSocket API -->
<dependency>
<groupId>jakarta.websocket</groupId>
<artifactId>jakarta.websocket-client-api</artifactId>
<version>2.1.0</version>
</dependency>
<!-- Apache HttpClient for REST calls -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.2.1</version>
</dependency>
<!-- Metrics -->
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
<version>4.2.18</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.3</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.9</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
</dependencies>

Example WebSocket Server for Testing

Example 1: Simple WebSocket Server

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
import jakarta.websocket.*;
import jakarta.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
@ServerEndpoint("/chat")
public class ChatWebSocketServer {
private static final Map<String, Session> sessions = new ConcurrentHashMap<>();
private static final AtomicInteger connectionCount = new AtomicInteger(0);
private static final AtomicInteger messageCount = new AtomicInteger(0);
@OnOpen
public void onOpen(Session session) {
sessions.put(session.getId(), session);
int count = connectionCount.incrementAndGet();
System.out.println("Client connected: " + session.getId() + ", Total connections: " + count);
// Send welcome message
sendMessage(session, "Welcome! You are client #" + count);
}
@OnMessage
public void onMessage(String message, Session session) {
int count = messageCount.incrementAndGet();
System.out.println("Received message from " + session.getId() + ": " + message);
System.out.println("Total messages processed: " + count);
// Echo the message back
sendMessage(session, "Echo: " + message);
// Broadcast to all connected clients
broadcast("Client " + session.getId() + " said: " + message);
}
@OnClose
public void onClose(Session session, CloseReason closeReason) {
sessions.remove(session.getId());
int count = connectionCount.decrementAndGet();
System.out.println("Client disconnected: " + session.getId() + 
", Reason: " + closeReason.getReasonPhrase() + 
", Remaining connections: " + count);
}
@OnError
public void onError(Session session, Throwable throwable) {
System.err.println("Error for client " + session.getId() + ": " + throwable.getMessage());
}
private void sendMessage(Session session, String message) {
try {
session.getBasicRemote().sendText(message);
} catch (IOException e) {
System.err.println("Failed to send message to " + session.getId() + ": " + e.getMessage());
}
}
private void broadcast(String message) {
sessions.values().forEach(session -> sendMessage(session, message));
}
public static int getConnectionCount() {
return connectionCount.get();
}
public static int getMessageCount() {
return messageCount.get();
}
public static void resetCounters() {
connectionCount.set(0);
messageCount.set(0);
}
// Server setup
public static Server createServer(int port) {
Server server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(port);
server.addConnector(connector);
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
server.setHandler(context);
// Configure WebSocket
JettyWebSocketServletContainerInitializer.configure(context, (servletContext, wsContainer) -> {
wsContainer.setMaxTextMessageBufferSize(65536);
wsContainer.setIdleTimeout(300000L); // 5 minutes
});
return server;
}
}

Custom WebSocket Load Testing Framework

Example 2: WebSocket Load Tester

import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import jakarta.websocket.*;
import java.net.URI;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
public class WebSocketLoadTester {
private final String serverUrl;
private final int totalConnections;
private final int messagesPerConnection;
private final long messageIntervalMs;
private final ExecutorService executor;
private final AtomicInteger successfulConnections = new AtomicInteger(0);
private final AtomicInteger failedConnections = new AtomicInteger(0);
private final AtomicLong totalMessagesSent = new AtomicLong(0);
private final AtomicLong totalMessagesReceived = new AtomicLong(0);
private final AtomicLong totalResponseTime = new AtomicLong(0);
public WebSocketLoadTester(String serverUrl, int totalConnections, 
int messagesPerConnection, long messageIntervalMs) {
this.serverUrl = serverUrl;
this.totalConnections = totalConnections;
this.messagesPerConnection = messagesPerConnection;
this.messageIntervalMs = messageIntervalMs;
this.executor = Executors.newFixedThreadPool(totalConnections);
}
public LoadTestResult runTest() throws Exception {
System.out.println("Starting WebSocket load test...");
System.out.println("Connections: " + totalConnections + 
", Messages per connection: " + messagesPerConnection);
CountDownLatch connectionLatch = new CountDownLatch(totalConnections);
CountDownLatch completionLatch = new CountDownLatch(totalConnections);
long startTime = System.currentTimeMillis();
// Create and start all client connections
for (int i = 0; i < totalConnections; i++) {
final int clientId = i;
executor.submit(() -> {
try {
runClient(clientId, connectionLatch, completionLatch);
} catch (Exception e) {
System.err.println("Client " + clientId + " failed: " + e.getMessage());
failedConnections.incrementAndGet();
connectionLatch.countDown();
completionLatch.countDown();
}
});
}
// Wait for all connections to be established
connectionLatch.await(2, TimeUnit.MINUTES);
// Wait for all clients to complete their message cycles
completionLatch.await(10, TimeUnit.MINUTES);
long endTime = System.currentTimeMillis();
long totalDuration = endTime - startTime;
executor.shutdown();
return new LoadTestResult(
successfulConnections.get(),
failedConnections.get(),
totalMessagesSent.get(),
totalMessagesReceived.get(),
totalResponseTime.get(),
totalDuration
);
}
private void runClient(int clientId, CountDownLatch connectionLatch, 
CountDownLatch completionLatch) throws Exception {
WebSocketClient client = new WebSocketClient();
client.start();
try {
LoadTestClientSocket socket = new LoadTestClientSocket(clientId);
ClientUpgradeRequest request = new ClientUpgradeRequest();
URI echoUri = new URI(serverUrl);
client.connect(socket, echoUri, request);
// Wait for connection
if (socket.awaitConnection(30, TimeUnit.SECONDS)) {
successfulConnections.incrementAndGet();
connectionLatch.countDown();
// Send messages
for (int i = 0; i < messagesPerConnection; i++) {
String message = String.format("Client-%d-Message-%d", clientId, i);
long sendTime = System.currentTimeMillis();
socket.sendMessage(message, sendTime);
totalMessagesSent.incrementAndGet();
// Wait before sending next message
if (messageIntervalMs > 0) {
Thread.sleep(messageIntervalMs);
}
}
// Wait for all responses
socket.awaitCompletion(1, TimeUnit.MINUTES);
totalMessagesReceived.addAndGet(socket.getMessagesReceived());
totalResponseTime.addAndGet(socket.getTotalResponseTime());
} else {
failedConnections.incrementAndGet();
connectionLatch.countDown();
}
} finally {
completionLatch.countDown();
client.stop();
}
}
@ClientEndpoint
public static class LoadTestClientSocket {
private final int clientId;
private Session session;
private final CountDownLatch connectionLatch = new CountDownLatch(1);
private final CountDownLatch completionLatch;
private final AtomicInteger messagesReceived = new AtomicInteger(0);
private final AtomicLong totalResponseTime = new AtomicLong(0);
public LoadTestClientSocket(int clientId) {
this.clientId = clientId;
this.completionLatch = new CountDownLatch(1);
}
@OnOpen
public void onOpen(Session session) {
this.session = session;
System.out.println("Client " + clientId + " connected");
connectionLatch.countDown();
}
@OnMessage
public void onMessage(String message, Session session) {
long receiveTime = System.currentTimeMillis();
// Extract send time from message if possible, or use other tracking mechanism
// For this example, we'll just count the message
messagesReceived.incrementAndGet();
// Simple response time calculation (would need message tracking in real scenario)
totalResponseTime.addAndGet(10); // Placeholder
if (message.contains("Final")) {
completionLatch.countDown();
}
}
@OnClose
public void onClose(Session session, CloseReason closeReason) {
System.out.println("Client " + clientId + " disconnected: " + closeReason.getReasonPhrase());
completionLatch.countDown();
}
@OnError
public void onError(Session session, Throwable throwable) {
System.err.println("Client " + clientId + " error: " + throwable.getMessage());
}
public boolean awaitConnection(long timeout, TimeUnit unit) throws InterruptedException {
return connectionLatch.await(timeout, unit);
}
public void awaitCompletion(long timeout, TimeUnit unit) throws InterruptedException {
completionLatch.await(timeout, unit);
}
public void sendMessage(String message, long sendTime) {
try {
String trackedMessage = message + "|" + sendTime;
session.getBasicRemote().sendText(trackedMessage);
} catch (Exception e) {
System.err.println("Client " + clientId + " failed to send message: " + e.getMessage());
}
}
public int getMessagesReceived() {
return messagesReceived.get();
}
public long getTotalResponseTime() {
return totalResponseTime.get();
}
}
public static class LoadTestResult {
public final int successfulConnections;
public final int failedConnections;
public final long totalMessagesSent;
public final long totalMessagesReceived;
public final long totalResponseTime;
public final long totalDurationMs;
public LoadTestResult(int successfulConnections, int failedConnections,
long totalMessagesSent, long totalMessagesReceived,
long totalResponseTime, long totalDurationMs) {
this.successfulConnections = successfulConnections;
this.failedConnections = failedConnections;
this.totalMessagesSent = totalMessagesSent;
this.totalMessagesReceived = totalMessagesReceived;
this.totalResponseTime = totalResponseTime;
this.totalDurationMs = totalDurationMs;
}
public void printSummary() {
System.out.println("\n=== LOAD TEST SUMMARY ===");
System.out.println("Successful connections: " + successfulConnections);
System.out.println("Failed connections: " + failedConnections);
System.out.println("Total messages sent: " + totalMessagesSent);
System.out.println("Total messages received: " + totalMessagesReceived);
System.out.println("Message success rate: " + 
String.format("%.2f%%", (double) totalMessagesReceived / totalMessagesSent * 100));
System.out.println("Average response time: " + 
(totalMessagesReceived > 0 ? totalResponseTime / totalMessagesReceived : 0) + "ms");
System.out.println("Total duration: " + totalDurationMs + "ms");
System.out.println("Messages per second: " + 
(totalDurationMs > 0 ? (totalMessagesSent * 1000) / totalDurationMs : 0));
}
}
public static void main(String[] args) throws Exception {
String serverUrl = "ws://localhost:8080/chat";
WebSocketLoadTester tester = new WebSocketLoadTester(serverUrl, 100, 10, 100);
LoadTestResult result = tester.runTest();
result.printSummary();
}
}

Advanced Load Testing with Metrics

Example 3: Metrics-Based Load Tester

import com.codahale.metrics.*;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import java.net.URI;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class MetricsWebSocketLoadTester {
private final MetricRegistry metrics = new MetricRegistry();
private final String serverUrl;
private final int totalUsers;
private final int rampUpSeconds;
private final int durationSeconds;
private final int messagesPerSecond;
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(10);
private final ExecutorService userExecutor = Executors.newCachedThreadPool();
// Metrics
private final Meter messagesSent = metrics.meter("messages-sent");
private final Meter messagesReceived = metrics.meter("messages-received");
private final Meter connectionErrors = metrics.meter("connection-errors");
private final Timer responseTime = metrics.timer("response-time");
private final Counter activeConnections = metrics.counter("active-connections");
private final Histogram messageSize = metrics.histogram("message-size");
public MetricsWebSocketLoadTester(String serverUrl, int totalUsers, int rampUpSeconds,
int durationSeconds, int messagesPerSecond) {
this.serverUrl = serverUrl;
this.totalUsers = totalUsers;
this.rampUpSeconds = rampUpSeconds;
this.durationSeconds = durationSeconds;
this.messagesPerSecond = messagesPerSecond;
}
public void runTest() throws Exception {
System.out.println("Starting metrics-based WebSocket load test...");
// Start metrics reporter
startMetricsReporter();
// Ramp up users
rampUpUsers();
// Run at steady state
Thread.sleep(durationSeconds * 1000L);
// Stop test
stopTest();
// Print final report
printFinalReport();
}
private void rampUpUsers() {
int usersPerSecond = totalUsers / rampUpSeconds;
for (int i = 0; i < totalUsers; i++) {
final int userId = i;
userExecutor.submit(() -> startUser(userId));
if (i % usersPerSecond == 0 && i > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
private void startUser(int userId) {
try {
WebSocketClient client = new WebSocketClient();
client.start();
MetricsClientSocket socket = new MetricsClientSocket(userId, metrics);
client.connect(socket, new URI(serverUrl));
activeConnections.inc();
// Send messages at specified rate
ScheduledFuture<?> messageTask = scheduler.scheduleAtFixedRate(() -> {
if (socket.isConnected()) {
String message = generateTestMessage(userId);
messageSize.update(message.length());
Timer.Context context = responseTime.time();
socket.sendMessage(message);
messagesSent.mark();
// In real implementation, you'd track the message and complete the timer when response received
context.close();
}
}, 0, 1000 / messagesPerSecond, TimeUnit.MILLISECONDS);
// Stop sending after test duration
scheduler.schedule(() -> {
messageTask.cancel(true);
try {
client.stop();
} catch (Exception e) {
System.err.println("Error stopping client for user " + userId + ": " + e.getMessage());
}
activeConnections.dec();
}, durationSeconds + rampUpSeconds, TimeUnit.SECONDS);
} catch (Exception e) {
connectionErrors.mark();
System.err.println("Failed to start user " + userId + ": " + e.getMessage());
}
}
private String generateTestMessage(int userId) {
return String.format("{\"userId\": %d, \"timestamp\": %d, \"payload\": \"%s\"}",
userId, System.currentTimeMillis(), "x".repeat(100)); // 100-byte payload
}
private void startMetricsReporter() {
ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics)
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.build();
reporter.start(10, TimeUnit.SECONDS);
// Stop reporter when test ends
scheduler.schedule(reporter::stop, durationSeconds + rampUpSeconds + 5, TimeUnit.SECONDS);
}
private void stopTest() {
userExecutor.shutdown();
scheduler.shutdown();
try {
if (!userExecutor.awaitTermination(30, TimeUnit.SECONDS)) {
userExecutor.shutdownNow();
}
if (!scheduler.awaitTermination(30, TimeUnit.SECONDS)) {
scheduler.shutdownNow();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void printFinalReport() {
System.out.println("\n=== FINAL LOAD TEST REPORT ===");
System.out.println("Total messages sent: " + messagesSent.getCount());
System.out.println("Total messages received: " + messagesReceived.getCount());
System.out.println("Connection errors: " + connectionErrors.getCount());
System.out.println("Average response time: " + 
responseTime.getSnapshot().getMean() + "ms");
System.out.println("95th percentile response time: " + 
responseTime.getSnapshot().get95thPercentile() + "ms");
}
@ClientEndpoint
public static class MetricsClientSocket {
private final int userId;
private final MetricRegistry metrics;
private Session session;
private volatile boolean connected = false;
public MetricsClientSocket(int userId, MetricRegistry metrics) {
this.userId = userId;
this.metrics = metrics;
}
@OnOpen
public void onOpen(Session session) {
this.session = session;
this.connected = true;
System.out.println("User " + userId + " connected successfully");
}
@OnMessage
public void onMessage(String message) {
metrics.meter("messages-received").mark();
// Process message and update metrics as needed
}
@OnClose
public void onClose(Session session, CloseReason closeReason) {
this.connected = false;
System.out.println("User " + userId + " disconnected: " + closeReason.getReasonPhrase());
}
@OnError
public void onError(Session session, Throwable throwable) {
metrics.meter("connection-errors").mark();
System.err.println("User " + userId + " error: " + throwable.getMessage());
}
public void sendMessage(String message) {
if (connected && session != null) {
try {
session.getBasicRemote().sendText(message);
} catch (Exception e) {
System.err.println("Failed to send message for user " + userId + ": " + e.getMessage());
}
}
}
public boolean isConnected() {
return connected;
}
}
public static void main(String[] args) throws Exception {
MetricsWebSocketLoadTester tester = new MetricsWebSocketLoadTester(
"ws://localhost:8080/chat", 
500,    // total users
30,     // ramp up over 30 seconds
120,    // run for 2 minutes
5       // 5 messages per second per user
);
tester.runTest();
}
}

Integration with JMeter

Example 4: JMeter WebSocket Test Plan Generator

import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.protocol.http.sampler.WebSocketSampler;
import org.apache.jmeter.protocol.http.control.HeaderManager;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.testelement.TestPlan;
import org.apache.jorphan.collections.HashTree;
import java.io.FileOutputStream;
import java.util.Properties;
public class JMeterWebSocketTestGenerator {
public static void generateWebSocketTestPlan(String outputPath) throws Exception {
HashTree testPlanTree = new HashTree();
// Create Test Plan
TestPlan testPlan = new TestPlan("WebSocket Load Test");
testPlan.setProperty(TestElement.TEST_CLASS, TestPlan.class.getName());
testPlan.setProperty(TestElement.GUI_CLASS, "TestPlanGui");
testPlanTree.add(testPlan);
// Create Thread Group
org.apache.jmeter.threads.ThreadGroup threadGroup = 
new org.apache.jmeter.threads.ThreadGroup();
threadGroup.setName("WebSocket Users");
threadGroup.setNumThreads(100);
threadGroup.setRampUp(30);
threadGroup.setDuration(300);
threadGroup.setProperty(TestElement.TEST_CLASS, 
org.apache.jmeter.threads.ThreadGroup.class.getName());
threadGroup.setProperty(TestElement.GUI_CLASS, "ThreadGroupGui");
HashTree threadGroupHash = testPlanTree.add(testPlan, threadGroup);
// Create WebSocket Sampler
WebSocketSampler webSocketSampler = new WebSocketSampler();
webSocketSampler.setName("WebSocket Echo Test");
webSocketSampler.setDomain("localhost");
webSocketSampler.setPort("8080");
webSocketSampler.setPath("/chat");
webSocketSampler.setProtocol("ws");
webSocketSampler.setImplementation("RFC6455");
webSocketSampler.setResponseTimeout("5000");
webSocketSampler.setConnectionTimeout("10000");
webSocketSampler.setStreamingConnection(true);
webSocketSampler.setRequestData("Hello from JMeter - ${__threadNum}");
// Add Header Manager if needed
HeaderManager headerManager = new HeaderManager();
headerManager.setName("WebSocket Headers");
headerManager.addHeader("Origin", "http://localhost:8080");
threadGroupHash.add(webSocketSampler);
threadGroupHash.add(headerManager);
// Add listeners
addListeners(threadGroupHash);
// Save test plan
saveTestPlan(testPlanTree, outputPath);
}
private static void addListeners(HashTree threadGroupHash) {
// Add Summary Report
org.apache.jmeter.reporters.ResultCollector summaryReport = 
new org.apache.jmeter.reporters.ResultCollector();
summaryReport.setName("Summary Report");
summaryReport.setProperty(TestElement.TEST_CLASS, 
org.apache.jmeter.reporters.ResultCollector.class.getName());
summaryReport.setProperty(TestElement.GUI_CLASS, "SummaryReport");
// Add View Results Tree
org.apache.jmeter.visualizers.ViewResultsFullVisualizer resultsTree = 
new org.apache.jmeter.visualizers.ViewResultsFullVisualizer();
resultsTree.setName("View Results Tree");
resultsTree.setProperty(TestElement.TEST_CLASS, 
org.apache.jmeter.visualizers.ViewResultsFullVisualizer.class.getName());
resultsTree.setProperty(TestElement.GUI_CLASS, "ViewResultsFullVisualizer");
threadGroupHash.add(summaryReport);
threadGroupHash.add(resultsTree);
}
private static void saveTestPlan(HashTree testPlanTree, String outputPath) throws Exception {
Properties props = new Properties();
props.setProperty("jmeter.save.saveservice.output_format", "xml");
props.setProperty("jmeter.save.saveservice.assertions", "true");
props.setProperty("jmeter.save.saveservice.bytes", "true");
org.apache.jmeter.save.SaveService.saveTree(testPlanTree, 
new FileOutputStream(outputPath), props);
System.out.println("JMeter test plan saved to: " + outputPath);
}
public static void main(String[] args) throws Exception {
generateWebSocketTestPlan("websocket_load_test.jmx");
}
}

Performance Monitoring

Example 5: Real-time Performance Monitor

import java.lang.management.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class WebSocketPerformanceMonitor {
private final ScheduledExecutorService monitorExecutor = Executors.newScheduledThreadPool(1);
private final MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
private final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
private final OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
private long lastGcCount = 0;
private long lastGcTime = 0;
public void startMonitoring() {
System.out.println("Starting performance monitoring...");
System.out.println("Time\tCPU%\tUsedMem\tMaxMem\tThreads\tActiveConn");
monitorExecutor.scheduleAtFixedRate(() -> {
try {
printMetrics();
} catch (Exception e) {
System.err.println("Error in performance monitoring: " + e.getMessage());
}
}, 0, 5, TimeUnit.SECONDS);
}
public void stopMonitoring() {
monitorExecutor.shutdown();
try {
if (!monitorExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
monitorExecutor.shutdownNow();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void printMetrics() {
long currentTime = System.currentTimeMillis();
// Memory usage
long usedMemory = memoryBean.getHeapMemoryUsage().getUsed() / (1024 * 1024);
long maxMemory = memoryBean.getHeapMemoryUsage().getMax() / (1024 * 1024);
// Thread count
int threadCount = threadBean.getThreadCount();
// GC statistics
long gcCount = 0;
long gcTime = 0;
for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) {
gcCount += gc.getCollectionCount();
gcTime += gc.getCollectionTime();
}
long gcCountDelta = gcCount - lastGcCount;
long gcTimeDelta = gcTime - lastGcTime;
lastGcCount = gcCount;
lastGcTime = gcTime;
// Active connections (you would get this from your WebSocket server)
int activeConnections = ChatWebSocketServer.getConnectionCount();
System.out.printf("%tT\t%.1f\t%dM\t%dM\t%d\t%d\t(GC: %d/%dms)%n",
currentTime,
getProcessCpuLoad(),
usedMemory,
maxMemory,
threadCount,
activeConnections,
gcCountDelta,
gcTimeDelta);
}
private double getProcessCpuLoad() {
if (osBean instanceof com.sun.management.OperatingSystemMXBean) {
return ((com.sun.management.OperatingSystemMXBean) osBean).getProcessCpuLoad() * 100;
}
return 0.0;
}
public static void main(String[] args) throws Exception {
WebSocketPerformanceMonitor monitor = new WebSocketPerformanceMonitor();
monitor.startMonitoring();
// Run for 5 minutes
Thread.sleep(300000);
monitor.stopMonitoring();
}
}

Best Practices for WebSocket Load Testing

1. Test Scenarios

public class WebSocketTestScenarios {
public enum TestScenario {
// Low concurrency, high message rate
CHAT_ROOM(50, 1000, 10, "Chat room simulation"),
// High concurrency, low message rate  
NOTIFICATIONS(1000, 10, 100, "Notification service"),
// Mixed workload
TRADING_PLATFORM(500, 100, 50, "Trading platform"),
// Stress test
STRESS_TEST(5000, 5, 200, "Maximum capacity test");
public final int connections;
public final int messagesPerMinute;
public final int messageSizeBytes;
public final String description;
TestScenario(int connections, int messagesPerMinute, 
int messageSizeBytes, String description) {
this.connections = connections;
this.messagesPerMinute = messagesPerMinute;
this.messageSizeBytes = messageSizeBytes;
this.description = description;
}
}
public static void runScenario(TestScenario scenario, String serverUrl) throws Exception {
System.out.println("Running scenario: " + scenario.description);
WebSocketLoadTester tester = new WebSocketLoadTester(
serverUrl,
scenario.connections,
scenario.messagesPerMinute,
60000 / scenario.messagesPerMinute // Convert to interval
);
LoadTestResult result = tester.runTest();
result.printSummary();
}
}

2. Key Metrics to Monitor

  • Connection establishment time
  • Message round-trip time
  • Connection stability and drop rate
  • Memory usage and GC activity
  • CPU utilization
  • Network I/O

Conclusion

WebSocket load testing requires specialized approaches due to the persistent nature of connections. Key strategies include:

  1. Use Appropriate Tools: Jetty WebSocket client for Java-based testing, JMeter for complex scenarios
  2. Monitor Key Metrics: Connection count, message throughput, response times, resource usage
  3. Simulate Realistic Workloads: Consider message rates, connection patterns, and payload sizes
  4. Implement Proper Cleanup: Ensure connections are properly closed to avoid resource leaks
  5. Use Metrics Libraries: For comprehensive performance monitoring and reporting

The provided examples give you a solid foundation for building comprehensive WebSocket load testing solutions tailored to your specific requirements.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper