/***********************************************************************************************************************
* Copyright (C) 2010-2014 by the Stratosphere project (http://stratosphere.eu)
*
* 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 eu.stratosphere.test.runtime;
import eu.stratosphere.configuration.Configuration;
import eu.stratosphere.core.io.IOReadableWritable;
import eu.stratosphere.nephele.jobgraph.DistributionPattern;
import eu.stratosphere.nephele.jobgraph.JobGenericInputVertex;
import eu.stratosphere.nephele.jobgraph.JobGraph;
import eu.stratosphere.nephele.jobgraph.JobGraphDefinitionException;
import eu.stratosphere.nephele.jobgraph.JobInputVertex;
import eu.stratosphere.nephele.jobgraph.JobOutputVertex;
import eu.stratosphere.nephele.jobgraph.JobTaskVertex;
import eu.stratosphere.nephele.template.AbstractGenericInputTask;
import eu.stratosphere.nephele.template.AbstractOutputTask;
import eu.stratosphere.nephele.template.AbstractTask;
import eu.stratosphere.runtime.io.api.RecordReader;
import eu.stratosphere.runtime.io.api.RecordWriter;
import eu.stratosphere.runtime.io.channels.ChannelType;
import eu.stratosphere.test.util.RecordAPITestBase;
import eu.stratosphere.util.LogUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.After;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@RunWith(Parameterized.class)
public class NetworkStackNepheleITCase extends RecordAPITestBase {
private static final Log LOG = LogFactory.getLog(NetworkStackNepheleITCase.class);
private static final String DATA_VOLUME_GB_CONFIG_KEY = "data.volume.gb";
private static final String USE_FORWARDER_CONFIG_KEY = "use.forwarder";
private static final String NUM_SUBTASKS_CONFIG_KEY = "num.subtasks";
private static final String NUM_SUBTASKS_PER_INSTANCE_CONFIG_KEY = "num.subtasks.instance";
private static final String IS_SLOW_SENDER_CONFIG_KEY = "is.slow.sender";
private static final String IS_SLOW_RECEIVER_CONFIG_KEY = "is.slow.receiver";
private static final int IS_SLOW_SLEEP_MS = 10;
private static final int IS_SLOW_EVERY_NUM_RECORDS = (2 * 32 * 1024) / SpeedTestRecord.RECORD_SIZE;
// ------------------------------------------------------------------------
public NetworkStackNepheleITCase(Configuration config) {
super(config);
setNumTaskManager(2);
LogUtils.initializeDefaultConsoleLogger();
}
@Parameters
public static Collection<Object[]> getConfigurations() {
Object[][] configParams = new Object[][]{
new Object[]{1, false, false, false, 4, 2},
new Object[]{1, true, false, false, 4, 2},
new Object[]{1, true, true, false, 4, 2},
new Object[]{1, true, false, true, 4, 2},
new Object[]{2, true, false, false, 4, 2},
new Object[]{4, true, false, false, 4, 2},
new Object[]{4, true, false, false, 8, 4},
new Object[]{4, true, false, false, 16, 8},
};
List<Configuration> configs = new ArrayList<Configuration>(configParams.length);
for (Object[] p : configParams) {
Configuration config = new Configuration();
config.setInteger(DATA_VOLUME_GB_CONFIG_KEY, (Integer) p[0]);
config.setBoolean(USE_FORWARDER_CONFIG_KEY, (Boolean) p[1]);
config.setBoolean(IS_SLOW_SENDER_CONFIG_KEY, (Boolean) p[2]);
config.setBoolean(IS_SLOW_RECEIVER_CONFIG_KEY, (Boolean) p[3]);
config.setInteger(NUM_SUBTASKS_CONFIG_KEY, (Integer) p[4]);
config.setInteger(NUM_SUBTASKS_PER_INSTANCE_CONFIG_KEY, (Integer) p[5]);
configs.add(config);
}
return toParameterList(configs);
}
// ------------------------------------------------------------------------
@Override
protected JobGraph getJobGraph() throws Exception {
int dataVolumeGb = this.config.getInteger(DATA_VOLUME_GB_CONFIG_KEY, 1);
boolean useForwarder = this.config.getBoolean(USE_FORWARDER_CONFIG_KEY, true);
boolean isSlowSender = this.config.getBoolean(IS_SLOW_SENDER_CONFIG_KEY, false);
boolean isSlowReceiver = this.config.getBoolean(IS_SLOW_RECEIVER_CONFIG_KEY, false);
int numSubtasks = this.config.getInteger(NUM_SUBTASKS_CONFIG_KEY, 1);
int numSubtasksPerInstance = this.config.getInteger(NUM_SUBTASKS_PER_INSTANCE_CONFIG_KEY, 1);
return createJobGraph(dataVolumeGb, useForwarder, isSlowSender, isSlowReceiver, numSubtasks, numSubtasksPerInstance);
}
@After
public void calculateThroughput() {
if (getJobExecutionResult() != null) {
int dataVolumeGb = this.config.getInteger(DATA_VOLUME_GB_CONFIG_KEY, 1);
double dataVolumeMbit = dataVolumeGb * 8192.0;
double runtimeSecs = getJobExecutionResult().getNetRuntime() / 1000.0;
int mbitPerSecond = (int) Math.round(dataVolumeMbit / runtimeSecs);
LOG.info(String.format("Test finished with throughput of %d MBit/s (" +
"runtime [secs]: %.2f, data volume [mbits]: %.2f)", mbitPerSecond, runtimeSecs, dataVolumeMbit));
}
}
private JobGraph createJobGraph(int dataVolumeGb, boolean useForwarder, boolean isSlowSender, boolean isSlowReceiver,
int numSubtasks, int numSubtasksPerInstance) throws JobGraphDefinitionException {
JobGraph jobGraph = new JobGraph("Speed Test");
JobInputVertex producer = new JobGenericInputVertex("Speed Test Producer", jobGraph);
producer.setInputClass(SpeedTestProducer.class);
producer.setNumberOfSubtasks(numSubtasks);
producer.setNumberOfSubtasksPerInstance(numSubtasksPerInstance);
producer.getConfiguration().setInteger(DATA_VOLUME_GB_CONFIG_KEY, dataVolumeGb);
producer.getConfiguration().setBoolean(IS_SLOW_SENDER_CONFIG_KEY, isSlowSender);
JobTaskVertex forwarder = null;
if (useForwarder) {
forwarder = new JobTaskVertex("Speed Test Forwarder", jobGraph);
forwarder.setTaskClass(SpeedTestForwarder.class);
forwarder.setNumberOfSubtasks(numSubtasks);
forwarder.setNumberOfSubtasksPerInstance(numSubtasksPerInstance);
}
JobOutputVertex consumer = new JobOutputVertex("Speed Test Consumer", jobGraph);
consumer.setOutputClass(SpeedTestConsumer.class);
consumer.setNumberOfSubtasks(numSubtasks);
consumer.setNumberOfSubtasksPerInstance(numSubtasksPerInstance);
consumer.getConfiguration().setBoolean(IS_SLOW_RECEIVER_CONFIG_KEY, isSlowReceiver);
if (useForwarder) {
producer.connectTo(forwarder, ChannelType.NETWORK, DistributionPattern.BIPARTITE);
forwarder.connectTo(consumer, ChannelType.NETWORK, DistributionPattern.BIPARTITE);
forwarder.setVertexToShareInstancesWith(producer);
consumer.setVertexToShareInstancesWith(producer);
}
else {
producer.connectTo(consumer, ChannelType.NETWORK, DistributionPattern.BIPARTITE);
producer.setVertexToShareInstancesWith(consumer);
}
return jobGraph;
}
// ------------------------------------------------------------------------
public static class SpeedTestProducer extends AbstractGenericInputTask {
private RecordWriter<SpeedTestRecord> writer;
@Override
public void registerInputOutput() {
this.writer = new RecordWriter<SpeedTestRecord>(this);
}
@Override
public void invoke() throws Exception {
this.writer.initializeSerializers();
// Determine the amount of data to send per subtask
int dataVolumeGb = getTaskConfiguration().getInteger(NetworkStackNepheleITCase.DATA_VOLUME_GB_CONFIG_KEY, 1);
long dataMbPerSubtask = (dataVolumeGb * 1024) / getCurrentNumberOfSubtasks();
long numRecordsToEmit = (dataMbPerSubtask * 1024 * 1024) / SpeedTestRecord.RECORD_SIZE;
LOG.info(String.format("%d/%d: Producing %d records (each record: %d bytes, total: %.2f GB)",
getIndexInSubtaskGroup() + 1, getCurrentNumberOfSubtasks(), numRecordsToEmit,
SpeedTestRecord.RECORD_SIZE, dataMbPerSubtask / 1024.0));
boolean isSlow = getTaskConfiguration().getBoolean(IS_SLOW_SENDER_CONFIG_KEY, false);
int numRecords = 0;
SpeedTestRecord record = new SpeedTestRecord();
for (long i = 0; i < numRecordsToEmit; i++) {
if (isSlow && (numRecords++ % IS_SLOW_EVERY_NUM_RECORDS) == 0) {
Thread.sleep(IS_SLOW_SLEEP_MS);
}
this.writer.emit(record);
}
this.writer.flush();
}
}
public static class SpeedTestForwarder extends AbstractTask {
private RecordReader<SpeedTestRecord> reader;
private RecordWriter<SpeedTestRecord> writer;
@Override
public void registerInputOutput() {
this.reader = new RecordReader<SpeedTestRecord>(this, SpeedTestRecord.class);
this.writer = new RecordWriter<SpeedTestRecord>(this);
}
@Override
public void invoke() throws Exception {
this.writer.initializeSerializers();
SpeedTestRecord record;
while ((record = this.reader.next()) != null) {
this.writer.emit(record);
}
this.writer.flush();
}
}
public static class SpeedTestConsumer extends AbstractOutputTask {
private RecordReader<SpeedTestRecord> reader;
@Override
public void registerInputOutput() {
this.reader = new RecordReader<SpeedTestRecord>(this, SpeedTestRecord.class);
}
@Override
public void invoke() throws Exception {
boolean isSlow = getTaskConfiguration().getBoolean(IS_SLOW_RECEIVER_CONFIG_KEY, false);
int numRecords = 0;
while (this.reader.next() != null) {
if (isSlow && (numRecords++ % IS_SLOW_EVERY_NUM_RECORDS) == 0) {
Thread.sleep(IS_SLOW_SLEEP_MS);
}
}
}
}
public static class SpeedTestRecord implements IOReadableWritable {
private static final int RECORD_SIZE = 128;
private final byte[] buf = new byte[RECORD_SIZE];
public SpeedTestRecord() {
for (int i = 0; i < RECORD_SIZE; ++i) {
this.buf[i] = (byte) (i % 128);
}
}
@Override
public void write(DataOutput out) throws IOException {
out.write(this.buf);
}
@Override
public void read(DataInput in) throws IOException {
in.readFully(this.buf);
}
}
}