/*
* Copyright 2015 the original author or authors.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.xd.loadgenerator;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.integration.endpoint.MessageProducerSupport;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
/**
* Is a source module that generates a series of fixed size messages to be
* dispatched to a stream. The load-generator is used to test the performance of
* XD in different environments.
*
* @author Glenn Renfro
* @author Marius Bogoevici
* @author Mark Pollack
*/
public class LoadGenerator extends MessageProducerSupport {
private static final TestMessageHeaders HEADERS = new TestMessageHeaders(null);
private final int producers;
private final int messageSize;
private final int messageCount;
private final boolean generateTimestamp;
private final AtomicBoolean running = new AtomicBoolean(false);
private ExecutorService executorService;
Logger logger = LoggerFactory.getLogger(LoadGenerator.class);
public LoadGenerator(int producers, int messageSize, int messageCount, boolean generateTimestamp) {
this.producers = producers;
this.messageSize = messageSize;
this.messageCount = messageCount;
this.generateTimestamp = generateTimestamp;
}
@Override
protected void doStart() {
executorService = Executors.newFixedThreadPool(producers);
if (running.compareAndSet(false, true)) {
for (int x = 0; x < producers; x++) {
executorService.execute(new Producer(x));
}
}
}
@Override
protected void doStop() {
if (running.compareAndSet(true, false)) {
executorService.shutdown();
}
}
protected class Producer implements Runnable {
int producerId;
public Producer(int producerId) {
this.producerId = producerId;
}
private void send() {
logger.info("Producer " + producerId + " sending " + messageCount + " messages");
for (int x = 0; x < messageCount; x++) {
final byte[] message = createPayload(x);
sendMessage(new TestMessage(message));
}
logger.info("All Messages Dispatched");
}
/**
* Creates a message for consumption by the load-generator sink. The payload will
* optionally contain a timestamp and sequence number if the generateTimestamp
* property is set to true.
*
* @param sequenceNumber a number to be prepended to the message
* @return a byte array containing a series of numbers that match the message size as
* specified by the messageSize constructor arg.
*/
private byte[] createPayload(int sequenceNumber) {
byte message[] = new byte[messageSize];
if (generateTimestamp) {
try {
ByteArrayOutputStream acc = new ByteArrayOutputStream();
DataOutputStream d = new DataOutputStream(acc);
long nano = System.nanoTime();
d.writeInt(sequenceNumber);
d.writeLong(nano);
d.flush();
acc.flush();
byte[] m = acc.toByteArray();
if (m.length <= messageSize) {
System.arraycopy(m, 0, message, 0, m.length);
return message;
} else {
return m;
}
} catch (IOException ioe) {
throw new IllegalStateException(ioe);
}
} else {
return message;
}
}
public void run() {
send();
}
private class TestMessage implements Message<byte[]> {
private final byte[] message;
private final TestMessageHeaders headers;
public TestMessage(byte[] message) {
this.message = message;
this.headers = HEADERS;
}
@Override
public byte[] getPayload() {
return message;
}
@Override
public MessageHeaders getHeaders() {
return headers;
}
}
}
@SuppressWarnings("serial")
private static class TestMessageHeaders extends MessageHeaders {
public TestMessageHeaders(Map<String, Object> headers) {
super(headers, ID_VALUE_NONE, -1L);
}
}
}