/* * Sun Public License * * The contents of this file are subject to the Sun Public License Version * 1.0 (the "License"). You may not use this file except in compliance with * the License. A copy of the License is available at http://www.sun.com/ * * The Original Code is the SLAMD Distributed Load Generation Engine. * The Initial Developer of the Original Code is Neil A. Wilson. * Portions created by Neil A. Wilson are Copyright (C) 2004-2010. * Some preexisting portions Copyright (C) 2002-2006 Sun Microsystems, Inc. * All Rights Reserved. * * Contributor(s): Neil A. Wilson */ package com.slamd.jobs; import java.io.IOException; import java.io.InputStream; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.ArrayList; import com.slamd.job.JobClass; import com.slamd.job.UnableToRunException; import com.slamd.parameter.BooleanParameter; import com.slamd.parameter.FileURLParameter; import com.slamd.parameter.FloatParameter; import com.slamd.parameter.IntegerParameter; import com.slamd.parameter.Parameter; import com.slamd.parameter.ParameterList; import com.slamd.parameter.PlaceholderParameter; import com.slamd.parameter.StringParameter; import com.slamd.stat.IncrementalTracker; import com.slamd.stat.StatTracker; /** * This class defines a SLAMD job that may be used to replay captured TCP * traffic against a target server. The traffic to replay should be in a * capture file using the format used by the TCPCapture tool. * * * @author Neil A. Wilson */ public class TCPReplayJobClass extends JobClass { /** * The display name of the stat tracker used to track the number of times that * the replay process caught a disconnect from the remote server. */ public static final String STAT_TRACKER_DISCONNECTS_CAUGHT = "Disconnects Caught"; /** * The display name of the stat tracker used to track the number of times that * the entire data set has been replayed. */ public static final String STAT_TRACKER_ITERATIONS_COMPLETED = "Iterations Completed"; /** * The display name of the stat tracker used to track the number of request * packets that the client replayed to the server. */ public static final String STAT_TRACKER_PACKETS_REPLAYED = "Request Packets Replayed"; // The parameter used to indicate whether to preserve the original timing. private BooleanParameter preserveTimingParameter = new BooleanParameter("preserve_timing", "Preserve Original Timing", "Indicates whether the client should attempt to " + "preserve the original timing measured when the " + "data was captured.", true); // The parameter used to specify the URL to the capture file. private FileURLParameter captureFileURLParameter = new FileURLParameter("capture_file_url", "Capture File URL", "The URL to the capture file containing the data " + "to replay.", null, true); // The parameter used to specify the multiplier for the time between requests. private FloatParameter multiplierParameter = new FloatParameter("timing_multiplier", "Timing Multiplier", "The value that should be multiplied to the time " + "between each request in an attempt to speed up or " + "slow down the replay by the specified factor. " + "This is ignored if the client should not try to " + "preserve the original timing.", true, (float) 1.0, true, (float) 0.0, false, (float) 0.0); // The parameter used to specify the delay between iterations. private IntegerParameter iterationDelayParamter = new IntegerParameter("iteration_delay", "Delay Between Iterations (ms)", "The length of time in milliseconds to sleep " + "between individual iterations of the entire " + "data set.", true, 0, true, 0, false, 0); // The parameter used to specify the maximum number of times to replay the // entire data set. private IntegerParameter maxIterationsParameter = new IntegerParameter("max_iterations", "Maximum Number of Iterations", "The maximum number of times that each thread " + "should attempt to replay the entire data set.", true, -1, false, 0, false, 0); // The parameter used to specify the delay between packets. private IntegerParameter packetDelayParameter = new IntegerParameter("packet_delay", "Fixed Delay Between Packets (ms)", "The length of time in milliseconds to sleep " + "between individual packets when replaying the " + "data set. This is ignored if the client should " + "try to preserve the original timing.", true, 0, true, 0, false, 0); // The parameter used to specify the port of the target server. private IntegerParameter portParameter = new IntegerParameter("port", "Target Server Port", "The port on the target server to which the " + "traffic should be replayed.", true, 0, true, 1, true, 65535); // A placeholder used for formatting. private PlaceholderParameter placeholder = new PlaceholderParameter(); // The parameter used to specify the address of the target server. private StringParameter hostParameter = new StringParameter("host", "Target Server Address", "The address of the target server to which the " + "traffic should be replayed.", true, ""); // Static variables corresponding to the parameter values. private static boolean preserveTiming; private static byte[][] captureData; private static float timingMultiplier; private static int delayBetweenIterations; private static int delayBetweenPackets; private static int maxIterations; private static int targetPort; private static int[] sleepTimes; private static String targetHost; // Define a thread that will be used to read the data coming back from the // server on all the target connections. private static TCPReplayReadThread readThread; // The socket channel that will be used to communicate with the target server. private SocketChannel socketChannel; // The stat trackers that will be used to capture statistical data. private IncrementalTracker disconnectsCaught; private IncrementalTracker iterationsCompleted; private IncrementalTracker packetsReplayed; /** * The default constructor used to create a new instance of the job class. * The only thing it should do is to invoke the superclass constructor. All * other initialization should be performed in the <CODE>initialize</CODE> * method. */ public TCPReplayJobClass() { super(); } /** * {@inheritDoc} */ @Override() public String getJobName() { return "TCP Replay"; } /** * {@inheritDoc} */ @Override() public String getShortDescription() { return "Replay captured TCP data against a target server"; } /** * {@inheritDoc} */ @Override() public String[] getLongDescription() { return new String[] { "This job can be used to replay captured TCP data against a target " + "server." }; } /** * {@inheritDoc} */ @Override() public String getJobCategoryName() { return "Utility"; } /** * {@inheritDoc} */ @Override() public ParameterList getParameterStubs() { Parameter[] params = new Parameter[] { placeholder, hostParameter, portParameter, captureFileURLParameter, placeholder, preserveTimingParameter, multiplierParameter, packetDelayParameter, placeholder, maxIterationsParameter, iterationDelayParamter }; return new ParameterList(params); } /** * {@inheritDoc} */ @Override() public StatTracker[] getStatTrackerStubs(String clientID, String threadID, int collectionInterval) { return new StatTracker[] { new IncrementalTracker(clientID, threadID, STAT_TRACKER_PACKETS_REPLAYED, collectionInterval), new IncrementalTracker(clientID, threadID, STAT_TRACKER_ITERATIONS_COMPLETED, collectionInterval), new IncrementalTracker(clientID, threadID, STAT_TRACKER_DISCONNECTS_CAUGHT, collectionInterval) }; } /** * {@inheritDoc} */ @Override() public StatTracker[] getStatTrackers() { return new StatTracker[] { packetsReplayed, iterationsCompleted, disconnectsCaught }; } /** * {@inheritDoc} */ @Override() public void initializeClient(String clientID, ParameterList parameters) throws UnableToRunException { // Get the values of the "simple" parameters. hostParameter = parameters.getStringParameter(hostParameter.getName()); if ((hostParameter != null) && hostParameter.hasValue()) { targetHost = hostParameter.getStringValue(); } portParameter = parameters.getIntegerParameter(portParameter.getName()); if ((portParameter != null) && portParameter.hasValue()) { targetPort = portParameter.getIntValue(); } preserveTimingParameter = parameters.getBooleanParameter(preserveTimingParameter.getName()); if (preserveTimingParameter != null) { preserveTiming = preserveTimingParameter.getBooleanValue(); } multiplierParameter = parameters.getFloatParameter(multiplierParameter.getName()); if ((multiplierParameter != null) && multiplierParameter.hasValue()) { timingMultiplier = multiplierParameter.getFloatValue(); } packetDelayParameter = parameters.getIntegerParameter(packetDelayParameter.getName()); if ((packetDelayParameter != null) && packetDelayParameter.hasValue()) { delayBetweenPackets = packetDelayParameter.getIntValue(); } maxIterationsParameter = parameters.getIntegerParameter(maxIterationsParameter.getName()); if ((maxIterationsParameter != null) && maxIterationsParameter.hasValue()) { maxIterations = maxIterationsParameter.getIntValue(); } iterationDelayParamter = parameters.getIntegerParameter(iterationDelayParamter.getName()); if ((iterationDelayParamter != null) && iterationDelayParamter.hasValue()) { delayBetweenIterations = iterationDelayParamter.getIntValue(); } // Now process the capture file contents. This logic is hard coded, so if // the capture format changes, then this will need to be re-written. ArrayList<byte[]> captureList = new ArrayList<byte[]>(); ArrayList<Long> timeList = new ArrayList<Long>(); try { captureFileURLParameter = parameters.getFileURLParameter(captureFileURLParameter.getName()); InputStream inputStream = captureFileURLParameter.getInputStream(); outerLoop: while (true) { // The first four bytes hold the capture version, which must be 1. int captureVersion = 0; for (int i=0; i < 4; i++) { int byteRead = inputStream.read(); if (byteRead < 0) { break outerLoop; } captureVersion = (captureVersion << 8) | (byteRead & 0xFF); } if (captureVersion != 1) { throw new UnableToRunException("Unable to parse the capture file: " + "The capture version must be 1, but " + "a value of " + captureVersion + " was read."); } // The next eight bytes hold the timestamp. long captureTime = 0; for (int i=0; i < 8; i++) { captureTime= (captureTime << 8) | (inputStream.read() & 0xFF); } // The next four bytes hold the number of bytes in the capture. int captureLength = 0; for (int i=0; i < 4; i++) { captureLength = (captureLength << 8) | (inputStream.read() & 0xFF); } if ((captureLength < 0) || (captureLength > (1024*1024))) { throw new UnableToRunException("Unable to parse the capture file: " + "The capture length must not be " + "greater than one megabyte (decoded " + "a length of " + captureLength + ")."); } // Read the specified number of bytes. byte[] data = new byte[captureLength]; int totalBytesRead = 0; while (totalBytesRead < captureLength) { int bytesRead = inputStream.read(data, totalBytesRead, (captureLength - totalBytesRead)); if (bytesRead < 0) { throw new UnableToRunException("Unable to parse the capture " + "file: The input stream was " + "unexpectedly closed before all " + "data could be read."); } totalBytesRead += bytesRead; } captureList.add(data); timeList.add(captureTime); } long lastTime = -1; captureData = new byte[captureList.size()][]; sleepTimes = new int[captureData.length]; for (int i=0; i < captureData.length; i++) { captureData[i] = captureList.get(i); if (i == 0) { sleepTimes[i] = 0; lastTime = timeList.get(i); } else if (preserveTiming) { long captureTime = timeList.get(i); sleepTimes[i] = (int) ((captureTime - lastTime) * timingMultiplier); lastTime = captureTime; } else { sleepTimes[i] = delayBetweenPackets; } } } catch (UnableToRunException utre) { throw utre; } catch (Exception e) { throw new UnableToRunException("Unable to parse the capture file: " + stackTraceToString(e)); } try { readThread = new TCPReplayReadThread(this); } catch (Exception e) { throw new UnableToRunException("Could not create the read thread: " + stackTraceToString(e)); } } /** * {@inheritDoc} */ @Override() public void initializeThread(String clientID, String threadID, int collectionInterval, ParameterList parameters) throws UnableToRunException { packetsReplayed = new IncrementalTracker(clientID, threadID, STAT_TRACKER_PACKETS_REPLAYED, collectionInterval); iterationsCompleted = new IncrementalTracker(clientID, threadID, STAT_TRACKER_ITERATIONS_COMPLETED, collectionInterval); disconnectsCaught = new IncrementalTracker(clientID, threadID, STAT_TRACKER_DISCONNECTS_CAUGHT, collectionInterval); socketChannel = null; readThread.registerJobThread(); } /** * {@inheritDoc} */ @Override() public void runJob() { try { packetsReplayed.startTracker(); iterationsCompleted.startTracker(); disconnectsCaught.startTracker(); boolean connected = false; boolean infinite = (maxIterations <= 0); InetSocketAddress socketAddress = new InetSocketAddress(targetHost, targetPort); outerLoop: for (int i=0; (infinite || (i < maxIterations)); i++) { if (shouldStop()) { break; } for (int j=0; j < captureData.length; j++) { if (shouldStop()) { break outerLoop; } if (! connected) { try { socketChannel = SocketChannel.open(socketAddress); socketChannel.configureBlocking(false); socketChannel.finishConnect(); readThread.registerSocketChannel(socketChannel); connected = true; } catch (IOException ioe) { logMessage("ERROR: Unable to connect to " + socketAddress + " -- " + stackTraceToString(ioe)); break outerLoop; } } if (sleepTimes[j] > 0) { Thread.sleep(sleepTimes[j]); } try { ByteBuffer buffer = ByteBuffer.wrap(captureData[j]); int bytesWritten = socketChannel.write(buffer); while (bytesWritten < captureData[j].length) { bytesWritten += socketChannel.write(buffer); } packetsReplayed.increment(); } catch (IOException ioe) { try { socketChannel.close(); } catch (Exception e) {} connected = false; disconnectsCaught.increment(); } } if (! shouldStop()) { iterationsCompleted.increment(); } } } catch (Exception e) { logMessage("ERROR: Uncaught exception during processing -- " + stackTraceToString(e)); } finally { readThread.threadDone(); packetsReplayed.stopTracker(); iterationsCompleted.stopTracker(); disconnectsCaught.stopTracker(); } } /** * {@inheritDoc} */ @Override() public void destroyThread() { try { socketChannel.close(); } catch (Exception e) {} } }