/* * 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.tools.tcpreplay; import java.io.FileInputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.ArrayList; import java.util.Iterator; /** * This class defines a utility that may be used to replay capture data against * the same or a different server using one or more threads. It can perform * this replay either as quickly as possible, or it can attempt to preserve the * original timing (or play back requests at some fraction thereof). * * * @author Neil A. Wilson */ public class ReplayCapture { // The list of socket channels that are pending registration with the // selector. private ArrayList<SocketChannel> pendingRegisterList = new ArrayList<SocketChannel>(); // Indicates whether the replay client should attempt to preserve the original // timing associated with the requests. private boolean preserveTiming; // Indicates whether a request has been received to stop the replay. protected boolean stopRequested; // The data that is to be replayed against the target server. protected CaptureData[] captureData; // The multiplier that should be applied to the time between requests when // attempting to preserve the original timing. private float timeMultiplier; // The number of threads that are currently active. private int activeThreads; // The delay in milliseconds that should be used between iterations of the // capture data set. private int delayBetweenIterations; // The delay in milliseconds that should be used between individual request // captures if the original timing is not going to be preserved. private int delayBetweenRequests; // The maximum length of time in seconds to spend replaying data. private int maxDuration; // The maximum number of times that the entire capture set should be replayed. protected int numIterations; // The number of concurrent threads that should be used to perform the replay. private int numThreads; // The port of the target server against which the requests are to be // replayed. protected int targetPort; // The length of time in milliseconds to sleep between each capture data // packet if we are attempting to preserve the original timing. protected int[] sleepTimes; // The number of times the connection was unexpectedly closed during // processing. private long totalDisconnects; // The number of iterations through the entire data set that were completed. private long totalIterationsCompleted; // The number of individual capture packets that were replayed. private long totalPacketsReplayed; // The threads that will be used to actually perform the replay process. private ReplayThread[] replayThreads; // The selector that will be used to read data from the backend server // whenever it arrives. private Selector selector; // The address of the target server against which the requests are to be // replayed. protected String targetHost; /** * Creates a new instance of this replay utility with the provided * information. * * @param targetHost The address of the target server against * which to replay the the capture data. * @param targetPort The port of the target server against which * to replay the capture data. * @param captureFile The path to the file containing the capture * data to replay. * @param preserveTiming Indicates whether an attempt should be made * to replay packets based on the original * times between each request packet. * @param timeMultiplier A multiplier that will be applied to the * time between individual packets if the * original timing is to be preserved. A * value of 1.0 will preserve the original * timing, while 0.5 will sleep half as long * (and therefore try to go twice as fast), * and 2.0 will sleep twice as long (and try * to go twice as slow). * @param delayBetweenRequests The length of time in milliseconds to sleep * between each capture packet replayed if the * original timing is not to be preserved. * @param numIterations The maximum number of times the entire * capture data set should be replayed (-1 for * no limit). * @param delayBetweenIterations The length of time in milliseconds to sleep * between iterations through the entire * capture data set. * @param maxDuration The maximum length of time in seconds to * spend replaying the capture data (-1 for no * limit). * @param numThreads The number of threads to concurrently * replay the captured data against the target * server. * * @throws IOException If a problem occurs while attempting to read the * capture file containing the data to replay. * * @throws CaptureException If a problem occurs while trying to parse the * capture file. */ public ReplayCapture(String targetHost, int targetPort, String captureFile, boolean preserveTiming, float timeMultiplier, int delayBetweenRequests, int numIterations, int delayBetweenIterations, int maxDuration, int numThreads) throws IOException, CaptureException { // Set the values of all the instance variables based on the arguments. this.targetHost = targetHost; this.targetPort = targetPort; this.preserveTiming = preserveTiming; this.timeMultiplier = timeMultiplier; this.delayBetweenRequests = delayBetweenRequests; this.numIterations = numIterations; this.delayBetweenIterations = delayBetweenIterations; this.maxDuration = maxDuration; this.numThreads = numThreads; stopRequested = false; activeThreads = 0; totalDisconnects = 0; totalIterationsCompleted = 0; totalPacketsReplayed = 0; // Parse the capture file and read the data into memory. ArrayList<CaptureData> captureList = new ArrayList<CaptureData>(); FileInputStream inputStream = new FileInputStream(captureFile); while (true) { CaptureData capture = CaptureData.decodeFrom(inputStream); if (capture == null) { break; } else { captureList.add(capture); } } inputStream.close(); captureData = new CaptureData[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; } else if (preserveTiming) { sleepTimes[i] = (int) ((captureData[i].getCaptureTime() - captureData[i-1].getCaptureTime()) * timeMultiplier); } else { sleepTimes[i] = Math.max(0, delayBetweenRequests); } } // Create the appropriate number of replay threads, but don't start them // yet. replayThreads = new ReplayThread[numThreads]; for (int i=0; i < numThreads; i++) { replayThreads[i] = new ReplayThread(this); } // Create the selector that will be used to read data any responses that // might arrive from the target server. pendingRegisterList = new ArrayList<SocketChannel>(); selector = Selector.open(); // Register a shutdown hook so that the replay will be stopped gracefully // when the JVM is shutting down. Runtime.getRuntime().addShutdownHook(new ReplayShutdownThread(this)); } /** * Starts all the individual replay threads, then will loop, waiting for new * data to be available for reading on any of the connections established to * the target server. */ public void replayData() { activeThreads = numThreads; for (int i=0; i < numThreads; i++) { replayThreads[i].start(); } long stopTime; if (maxDuration > 0) { stopTime = System.currentTimeMillis() + (1000 * maxDuration); } else { stopTime = Long.MAX_VALUE; } boolean consecutiveFailures = false; ByteBuffer buffer = ByteBuffer.allocate(4096); while ((! stopRequested) && (System.currentTimeMillis() < stopTime)) { try { // Perform a select to see if there is any data available for reading. int numKeys = selector.select(100); if (numKeys > 0) { Iterator iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()) { SelectionKey selectionKey = (SelectionKey) iterator.next(); if (selectionKey.isReadable()) { SocketChannel channel = (SocketChannel) selectionKey.channel(); if (channel.isConnected()) { int bytesRead = channel.read(buffer); buffer.clear(); while (bytesRead > 0) { bytesRead = channel.read(buffer); buffer.clear(); } if (bytesRead < 0) { selectionKey.cancel(); } } else { selectionKey.cancel(); } } iterator.remove(); } } // See if there are any new connections that need to be registered. synchronized (pendingRegisterList) { if (! pendingRegisterList.isEmpty()) { Iterator<SocketChannel> iterator = pendingRegisterList.iterator(); while (iterator.hasNext()) { SocketChannel socketChannel = iterator.next(); socketChannel.register(selector, SelectionKey.OP_READ); } pendingRegisterList.clear(); } } // Reset the consecutive failures flag since this iteration was // successful. consecutiveFailures = false; } catch (Exception e) { System.err.println("Caught exception replay capture:"); e.printStackTrace(); if (consecutiveFailures) { // There have been multiple consecutive failures in the selector, so // exit gracefully. stopRequested = true; break; } else { consecutiveFailures = true; continue; } } } // If we have gotten here, then either a request has been received to stop // running or we have run for the maximum duration. Signal all the other // threads to stop running. stopRequested = true; // Close all the connections that may still be associated with the selector. synchronized (pendingRegisterList) { Iterator<SelectionKey> iterator = selector.keys().iterator(); while (iterator.hasNext()) { try { SelectionKey selectionKey = iterator.next(); selectionKey.channel().close(); } catch (Exception e) {} } } } /** * Registers a socket channel with this selector so that any data available * for reading on the channel will be consumed. * * @param socketChannel The socket channel that is to be registered with the * selector. */ public void registerSocketChannel(SocketChannel socketChannel) { synchronized (pendingRegisterList) { pendingRegisterList.add(socketChannel); } selector.wakeup(); } /** * Indicates that the specified thread has completed all the processing that * it will perform. * * @param replayThread The thread that has completed. */ public void threadDone(ReplayThread replayThread) { synchronized (pendingRegisterList) { totalDisconnects += replayThread.disconnects; totalIterationsCompleted += replayThread.iterationsCompleted; totalPacketsReplayed += replayThread.packetsReplayed; activeThreads--; if (activeThreads <= 0) { stopRequested = true; selector.wakeup(); } } } /** * Requests that the replay process stop as soon as possible. */ public void stopReplay() { stopRequested = true; selector.wakeup(); } /** * Waits for all the replay threads to complete before returning. */ public void waitForReplayThreads() { while (activeThreads > 0) { try { Thread.sleep(10); } catch (Exception e) {} } } /** * Retrieves the number of capture packets that have been read into memory. * * @return The number of capture packets that have been read into memory. */ public int getNumPackets() { return captureData.length; } /** * Retrieves the total number of disconnects that were encountered when all * threads were running. * * @return The total number of disconnects that were encountered when all * threads were running. */ public long getTotalDisconnects() { return totalDisconnects; } /** * Retrieves the total number of times that the replay threads made it * entirely through the capture data set (does not include partial iterations * completed). * * @return The total number of times that the replay threads made it entirely * through the capture data set. */ public long getTotalIterationsCompleted() { return totalIterationsCompleted; } /** * Retrieves the total number of request packets that the client replayed. * * @return The total number of request packets that the client replayed. */ public long getTotalPacketsReplayed() { return totalPacketsReplayed; } }