/* * eXist Request Replayer * * Release under the BSD License * * Copyright (c) 2006, Adam retter <adam.retter@devon.gov.uk> * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the Devon Portal Project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * $Id$ */ package org.exist.requestlog; import javax.swing.*; import javax.swing.border.TitledBorder; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowEvent; import java.io.BufferedReader; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.OutputStream; import java.io.RandomAccessFile; import java.net.Socket; /** Webapplication Descriptor * * Request Replayer Simple Swing GUI Application * Opens request replay log's as generated by eXist's web-application Descriptor when enabled * and can send the request's back to eXist. * Useful for load testing and checking memory leaks. * * @author Adam Retter <adam.retter@devon.gov.uk> * @serial 2006-02-28 * @version 1.6 */ public class RequestReplayer extends JFrame { private static final long serialVersionUID = 1L; /** Handle to the Request Log File */ private File requestLogFile = null; //Dialog Controls private JTextField txtReplayFilename = null; private JLabel lblRequestCount = null; private JTextField txtIterations = null; private JTextField txtAlternateHost = null; private JLabel lblWaitTimeRequest = null; private JTextField txtWaitTimeRequest = null; private JButton btnStart = null; private JTextArea txtStatus = null; /** * Entry point of the program * * @param args array of parameters passed in from where the program is executed */ public static void main(String[] args) { String fileName = null; if ( args.length > 0 ) fileName = args[0]; //Instantiate oursel // RequestReplayer rr = new RequestReplayer(fileName); } /** * Default Constructor * @param fileName */ public RequestReplayer(String fileName) { if( fileName != null ) requestLogFile = new File(fileName); initialize(); } /** * JDialog Window Event Handler * * @param e The event */ protected void processWindowEvent(WindowEvent e) { //Close Window Event if(e.getID() == WindowEvent.WINDOW_CLOSING) { this.setVisible(false); this.dispose(); System.exit(0); //why do we need this? shouldnt the above line do the job? } } /** * Initalise Dialog */ private void initialize() { setupGUI(); this.setSize(600, 430); this.setVisible(true); } /** *Setup the Dialog's GUI */ private void setupGUI() { this.setTitle("eXist Request Replayer"); //Dialog Content Panel JPanel cnt = new JPanel(); GridBagLayout grid = new GridBagLayout(); cnt.setLayout(grid); this.setContentPane(cnt); //Constraints for layout GridBagConstraints c = new GridBagConstraints(); c.insets = new Insets(2, 2, 2, 2); //Panel to hold controls relating to the request log file JPanel panelFile = new JPanel(); panelFile.setBorder(new TitledBorder("Request Log")); GridBagLayout panelFileGrid = new GridBagLayout(); panelFile.setLayout(panelFileGrid); //filename label JLabel lblLogFile = new JLabel("Filename:"); c.gridx = 0; c.gridy = 0; c.gridwidth = 1; c.anchor = GridBagConstraints.WEST; c.fill = GridBagConstraints.NONE; panelFileGrid.setConstraints(lblLogFile, c); panelFile.add(lblLogFile); // filename field String fileNameInfield = "/usr/local/eXist/request-replay-log.txt"; if( requestLogFile != null ) fileNameInfield = requestLogFile.getAbsolutePath(); txtReplayFilename = new JTextField(fileNameInfield, 24); txtReplayFilename.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if(!txtReplayFilename.getText().equals(requestLogFile.getPath())) { //Show a dialog to choose the log file chooseFile(); } } }); c.gridx = 1; c.gridy = 0; c.anchor = GridBagConstraints.WEST; c.fill = GridBagConstraints.NONE; panelFileGrid.setConstraints(txtReplayFilename, c); panelFile.add(txtReplayFilename); //filename choose button JButton btnChooseFile = new JButton("Choose..."); btnChooseFile.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { chooseFile(); } }); c.gridx = 2; c.gridy = 0; c.anchor = GridBagConstraints.WEST; c.fill = GridBagConstraints.NONE; panelFileGrid.setConstraints(btnChooseFile, c); panelFile.add(btnChooseFile); //Records count labels JLabel lblRequestCountText = new JLabel("Request Count: "); c.gridx = 0; c.gridy = 2; c.anchor = GridBagConstraints.WEST; c.fill = GridBagConstraints.NONE; panelFileGrid.setConstraints(lblRequestCountText, c); panelFile.add(lblRequestCountText); lblRequestCount = new JLabel(new Integer(countRequestRecords()).toString()); c.gridx = 1; c.gridy = 2; c.anchor = GridBagConstraints.WEST; c.fill = GridBagConstraints.NONE; panelFileGrid.setConstraints(lblRequestCount, c); panelFile.add(lblRequestCount); //Add the Request file panel to the main content c.gridx = 0; c.gridy = 0; c.anchor = GridBagConstraints.WEST; c.fill = GridBagConstraints.NONE; grid.setConstraints(panelFile, c); cnt.add(panelFile); //Panel to hold controls relating to the replaying of the requests JPanel panelReplay = new JPanel(); panelReplay.setBorder(new TitledBorder("Replay")); GridBagLayout panelReplayGrid = new GridBagLayout(); panelReplay.setLayout(panelReplayGrid); //Iterations Label JLabel lblIterations = new JLabel("Iterations:"); c.gridx = 0; c.gridy = 0; c.anchor = GridBagConstraints.WEST; c.fill = GridBagConstraints.NONE; panelReplayGrid.setConstraints(lblIterations, c); panelReplay.add(lblIterations); //Iterations field txtIterations = new JTextField("1", 5); c.gridx = 1; c.gridy = 0; c.anchor = GridBagConstraints.WEST; c.fill = GridBagConstraints.NONE; panelReplayGrid.setConstraints(txtIterations, c); panelReplay.add(txtIterations); //Alternate Host Label JLabel lblAlternateHost = new JLabel("Alternate Host:"); c.gridx = 0; c.gridy = 1; c.anchor = GridBagConstraints.WEST; c.fill = GridBagConstraints.NONE; panelReplayGrid.setConstraints(lblAlternateHost, c); panelReplay.add(lblAlternateHost); //Alternate Host Field txtAlternateHost = new JTextField(24); c.gridx = 1; c.gridy = 1; c.anchor = GridBagConstraints.WEST; c.fill = GridBagConstraints.NONE; panelReplayGrid.setConstraints(txtAlternateHost, c); panelReplay.add(txtAlternateHost); //Wait Time Request Label JLabel lblWaitTimeRequest = new JLabel("Delay between requests:"); c.gridx = 0; c.gridy = 2; c.anchor = GridBagConstraints.WEST; c.fill = GridBagConstraints.NONE; panelReplayGrid.setConstraints(lblWaitTimeRequest, c); panelReplay.add(lblWaitTimeRequest); //Wait Time Request Field txtWaitTimeRequest = new JTextField("200",6); c.gridx = 1; c.gridy = 2; c.anchor = GridBagConstraints.WEST; c.fill = GridBagConstraints.NONE; panelReplayGrid.setConstraints(txtWaitTimeRequest, c); panelReplay.add(txtWaitTimeRequest); //Start Button btnStart = new JButton("Start"); btnStart.setMnemonic('S'); btnStart.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { new Thread() { public void run() { doReplay(); } }.start(); //TODO: Could change the above method so iterations happen simultaneously in seperate threads! //Disable the Start Button (Only one Thread running at a time!) btnStart.setEnabled(false); } }); c.gridx = GridBagConstraints.CENTER; c.gridy = 2; c.anchor = GridBagConstraints.CENTER; c.fill = GridBagConstraints.HORIZONTAL; panelReplayGrid.setConstraints(btnStart, c); panelReplay.add(btnStart); //Add the Replay panel to the main content c.gridx = 0; c.gridy = 1; c.anchor = GridBagConstraints.WEST; c.fill = GridBagConstraints.NONE; grid.setConstraints(panelReplay, c); cnt.add(panelReplay); //Panel to hold controls for status of replaying of the requests JPanel panelStatus = new JPanel(); panelStatus.setBorder(new TitledBorder("Status")); GridBagLayout panelStatusGrid = new GridBagLayout(); panelStatus.setLayout(panelStatusGrid); //Status text area with vertical scroll txtStatus = new JTextArea(10, 40); txtStatus.setEditable(false); JScrollPane scrollStatus = new JScrollPane(txtStatus, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); c.gridx = 0; c.gridy = 0; c.anchor = GridBagConstraints.CENTER; c.fill = GridBagConstraints.NONE; panelStatusGrid.setConstraints(scrollStatus, c); panelStatus.add(scrollStatus); //Add the Status panel to the main content c.gridx = 0; c.gridy = 2; c.anchor = GridBagConstraints.WEST; c.fill = GridBagConstraints.NONE; grid.setConstraints(panelStatus, c); cnt.add(panelStatus); } /** * Counts the number of request records in the request replay log file * * @return The number of request records in the request replay log file */ private int countRequestRecords() { //Count of records int count = 0; //if there is no file handle try and setup a handle for the user specified file if(requestLogFile == null) { try { requestLogFile = new File(txtReplayFilename.getText()); } catch(NullPointerException npe) { System.err.println("Invalid path for Request file"); return(0); } } //Iterate through the file, incrementing the count for each record found //records start with a line starting with "Date: " try { BufferedReader bufRead = new BufferedReader(new FileReader(requestLogFile)); String line = bufRead.readLine(); while(line != null) { if(line.indexOf("Date:") > -1) { count++; } line = bufRead.readLine(); } bufRead.close(); } catch(FileNotFoundException fnfe) { System.err.println("Request file not found"); return(0); } catch(IOException ioe) { System.err.println("An I/O Exception occured whilst reading the Request file"); return(count); } return(count); } /** * Event for when the "Choose..." button is clicked, displays a simple * file chooser dialog * @param */ private void chooseFile() { JFileChooser fileChooser = new JFileChooser(); fileChooser.setApproveButtonText("Open"); fileChooser.setApproveButtonMnemonic('O'); if( requestLogFile != null ) { fileChooser.setCurrentDirectory( requestLogFile.getParentFile() ); fileChooser.ensureFileIsVisible(requestLogFile); } int retval = fileChooser.showDialog(this, null); if (retval == JFileChooser.APPROVE_OPTION) { requestLogFile = fileChooser.getSelectedFile(); txtReplayFilename.setText(requestLogFile.getPath()); lblRequestCount.setText(new Integer(countRequestRecords()).toString()); } } /** * Function that takes each request from the log file * and sends it back to the server */ private void doReplay() { RandomAccessFile raFile = null; //Random Access to Log File String line = null; //Holds a single line from the File long offset = 0; //Offset used for moving back up the file (for preserving carriage returns in a POST request) //clear the status txtStatus.setText(""); txtStatus.setCaretPosition(txtStatus.getDocument().getLength()); //repeat as specified by the user in the iteration text field int iterations = new Integer(txtIterations.getText()).intValue(); //Wait time trought request long WaitTime = new Long(txtWaitTimeRequest.getText()).longValue(); for(int i = 0; i < iterations; i++) { try { //try and open the file raFile = new RandomAccessFile(requestLogFile, "r"); //loop through the file line by line while((line = raFile.readLine()) != null) { /* * Each Request record start's with a line in the file starting "Date: " * and each record end's with two empty lines in the file (two carriage returns). * */ //Is this the start of a record if(line.indexOf("Date:") > -1) { //Yes, process the Record StringBuffer bufRequest = new StringBuffer(); //buffer to hold each request record from the file String server = null; //server to send the request to int port = 80; //server port to send the request to //Update Status for user txtStatus.append("Iteration: " + (i+1) + ", Sending Request from " + line + System.getProperty("line.separator")); txtStatus.setCaretPosition(txtStatus.getDocument().getLength()); //Loop through each line of the file that is part of this record while((line = raFile.readLine()) != null) { //is this an empty line (i.e. carriage return) if(line.length() != 0) { //NO, not an empty line (i.e. carriage return) //Store the line in the buffer bufRequest.append(line + System.getProperty("line.separator")); //host for request if(line.indexOf("Host:") > -1) { //has the user specified an alternate host? if(txtAlternateHost.getText().length() == 0) { //get host from request String host = line.substring(new String("Host: ").length()); server = host.substring(0, host.indexOf(":")); port = new Integer(host.substring(host.indexOf(":") + 1)).intValue(); } else { //get user specified alternate host server = txtAlternateHost.getText().substring(0, txtAlternateHost.getText().indexOf(":")); port = new Integer(txtAlternateHost.getText().substring(txtAlternateHost.getText().indexOf(":") + 1)).intValue(); } } } else { //YES, an empty line (i.e. carriage return) offset = raFile.getFilePointer(); //get the position in case this isnt the end of record indicator, then we can roll back //we have had an empty line, is it followed by another empty line? //if so this indicates the end of this record, so break out of the inner loop and read the next record if(raFile.readLine().length() == 0 || raFile.length() == offset) { //do request try { //Connect a socket to the server Socket socReq = new Socket(server, port); OutputStream socReqOut = socReq.getOutputStream(); DataOutputStream os = new DataOutputStream(socReqOut); DataInputStream is = new DataInputStream(socReq.getInputStream()); //Write Request to the socket os.writeBytes(bufRequest.toString()); os.flush(); try { String nextLine; int len = -1; while ((nextLine = is.readLine()) != null && nextLine.length() > 0) { if (nextLine.startsWith("Content-Length:")) len = Integer.parseInt(nextLine.substring(16)); System.out.println(nextLine); } System.out.println(); byte[] buf = new byte[512]; int byteCount = len; while (byteCount > 0) { int b; if (byteCount < 512) b = is.read(buf, 0, byteCount); else b = is.read(buf, 0, 512); if (b == -1) break; System.out.write(buf, 0, b); byteCount -= b; } } catch (IOException e) { } System.out.println(); is.close(); os.close(); socReqOut.close(); //Close the socket socReq.close(); } catch(IOException ioe) { System.err.println("An I/O Exception occured whilst writting a Request to the server"); System.err.println(ioe.getMessage()); } synchronized (this) { // wait 200 milliseconds before sending next request try { wait(WaitTime); } catch (InterruptedException e) { } } //break out of this inner while loop, i.e. next record break; } else { //wasnt the end of record marker so reset file position raFile.seek(offset); //Also Store the carriage return that we checked for in the buffer, //as this is part of the Request data not the end of file marker bufRequest.append(System.getProperty("line.separator")); } } } } } //close the file raFile.close(); } catch(IOException ioe) { System.err.println("An I/O Exception occured whilst reading the Request file"); //We have errored so enable the start button so the user can do it again if they want to! btnStart.setEnabled(true); return; } } //We have finished so enable the start button so the user can do it again if they want to! btnStart.setEnabled(true); } }