/* * 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.stat; import java.io.IOException; import java.net.Socket; import java.util.Vector; import javax.net.ssl.SSLSocketFactory; import netscape.ldap.LDAPException; import com.slamd.asn1.ASN1Element; import com.slamd.asn1.ASN1Enumerated; import com.slamd.asn1.ASN1Integer; import com.slamd.asn1.ASN1OctetString; import com.slamd.asn1.ASN1Reader; import com.slamd.asn1.ASN1Sequence; import com.slamd.asn1.ASN1Writer; import com.slamd.client.Client; import com.slamd.common.Constants; import com.slamd.common.SLAMDException; import com.slamd.jobs.JSSEBlindTrustSocketFactory; import com.slamd.message.ClientHelloMessage; import com.slamd.message.HelloResponseMessage; import com.slamd.message.Message; import com.slamd.message.RegisterStatisticMessage; import com.slamd.message.ReportStatisticMessage; import com.slamd.message.ServerShutdownMessage; /** * This class defines a component that can be used to report statistical data * to the SLAMD server in real-time. * * * @author Neil A. Wilson */ public class RealTimeStatReporter extends Thread { // The ASN.1 reader used to read messages from the SLAMD server. private ASN1Reader reader; // The ASN.1 writer used to write messages to the SLAMD server. private ASN1Writer writer; // Indicates whether this stat reporter should stop running. private boolean shouldStop; // The next message ID that should be sent to the server. private int nextMessageID; // The interval to use when reporting statistics back to the server. private int reportInterval; // The socket used to communicate with the SLAMD server. private Socket socket; // The job ID of the job for which we are currently reporting statistics. private String jobID; // The list of statistical data that should be sent to the monitor server. private Vector<ASN1Element[]> currentStatList; // The stat list that will become the current list whenever we are ready to // report to the server. private Vector<ASN1Element[]> standbyStatList; /** * Creates a new instance of this real-time stat reporter. * * @param serverAddress The address to use to connect to the SlAMD server * when reporting statistics. * @param serverStatPort The port number to use to connect to the SLAMD * server when reporting statistics. * @param reportInterval The interval at which statistics should be * reported to the server. * @param clientID The ID assigned to the client that will be * providing the statistics to report. * @param authID The ID to use to authenticate to the SLAMD * server. * @param authPW The password to use to authenticate to the SLAMD * server. * @param useSSL Indicates whether to use SSL to communicate with * the server. * @param blindTrust Indicates whether to blindly trust any SSL * certificate presented by the server. * @param sslKeyStore The JSSE key store to use. * @param sslKeyPassword The password to use to access the information in * the JSSE key store. * @param sslTrustStore The JSSE trust store to use. * @param sslTrustPassword The password to use to access the information in * the JSSE trust store. * * @throws SLAMDException If a problem occurs establishing the connection * to the SLAMD server. */ public RealTimeStatReporter(String serverAddress, int serverStatPort, int reportInterval, String clientID, String authID, String authPW, boolean useSSL, boolean blindTrust, String sslKeyStore, String sslKeyPassword, String sslTrustStore, String sslTrustPassword) throws SLAMDException { setName("Real-Time Stat Reporter"); this.reportInterval = reportInterval; nextMessageID = 1; // Establish the connection to the SLAMD server. if (useSSL) { if (blindTrust) { try { JSSEBlindTrustSocketFactory socketFactory = new JSSEBlindTrustSocketFactory(); socket = socketFactory.makeSocket(serverAddress, serverStatPort); } catch (LDAPException le) { throw new SLAMDException("Unable to create the stat reporter SSL " + "socket: " + le, le); } } else { if ((sslKeyStore != null) && (sslKeyStore.length() > 0)) { System.setProperty(Constants.SSL_KEY_STORE_PROPERTY, sslKeyStore); } if ((sslKeyPassword != null) && (sslKeyPassword.length() > 0)) { System.setProperty(Constants.SSL_KEY_PASSWORD_PROPERTY, sslKeyPassword); } if ((sslTrustStore != null) && (sslTrustStore.length() > 0)) { System.setProperty(Constants.SSL_TRUST_STORE_PROPERTY, sslTrustStore); } if ((sslTrustPassword != null) && (sslTrustPassword.length() > 0)) { System.setProperty(Constants.SSL_TRUST_PASSWORD_PROPERTY, sslTrustPassword); } try { SSLSocketFactory socketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); socket = socketFactory.createSocket(serverAddress, serverStatPort); } catch (IOException ioe) { throw new SLAMDException("Unable to create the stat reporter SSL " + "socket: " + ioe, ioe); } } } else { try { socket = new Socket(serverAddress, serverStatPort); } catch (IOException ioe) { throw new SLAMDException("Unable to create the stat reporter " + "socket: " + ioe, ioe); } } // Create the reader and writer for the socket. try { reader = new ASN1Reader(socket); writer = new ASN1Writer(socket); } catch (IOException ioe) { try { socket.close(); } catch (Exception e) {} throw new SLAMDException("Unable to create the reader or writer for " + "the stat reporter socket: " + ioe, ioe); } int authType = Constants.AUTH_TYPE_NONE; if ((authID != null) && (authID.length() > 0) && (authPW != null) && (authPW.length() > 0)) { authType = Constants.AUTH_TYPE_SIMPLE; } ClientHelloMessage helloRequest = new ClientHelloMessage(nextMessageID(), Client.SLAMD_CLIENT_VERSION, clientID, authType, authID, authPW, true); Message responseMessage = sendMessageAndReadResponse(helloRequest); if (responseMessage == null) { throw new SLAMDException("Unable to read the hello response from SLAMD " + "server"); } else if (! (responseMessage instanceof HelloResponseMessage)) { throw new SLAMDException("Received an inappropriate message from " + "the SLAMD server in response to a hello " + "request: " + responseMessage.toString()); } HelloResponseMessage helloResponse = (HelloResponseMessage) responseMessage; if (helloResponse.getResponseCode() != Constants.MESSAGE_RESPONSE_SUCCESS) { int respCode = helloResponse.getResponseCode(); String respMsg = helloResponse.getResponseMessage(); String message = "Unable to complete hello sequence with server -- " + "(err=" + respCode; if ((respMsg == null) || (respMsg.length() == 0)) { message += ")"; } else { message += ", msg=\"" + respMsg + "\")"; } throw new SLAMDException(message); } shouldStop = false; currentStatList = new Vector<ASN1Element[]>(); standbyStatList = new Vector<ASN1Element[]>(); } /** * Sends a message to the SLAMD server indicating that a new statistic will * be reported. This should be called for each individual stat tracker being * used (even for those running in separate threads on the same client). * * @param jobID The ID of the job for which the statistical data will * be provided. * @param statTracker The stat tracker being registered. */ public void registerStat(String jobID, StatTracker statTracker) { if (shouldStop) { return; } this.jobID = jobID; sendMessage(new RegisterStatisticMessage(nextMessageID(), jobID, statTracker.getClientID(), statTracker.getThreadID(), statTracker.getDisplayName())); } /** * Indicates that the provided value should be added to the corresponding * values for all threads by all clients for this statistic and interval. * * @param statTracker The stat tracker reporting the statistic. * @param intervalNumber The interval number for this value. * @param statValue the value being reported. */ public void reportStatToAdd(StatTracker statTracker, int intervalNumber, double statValue) { if (shouldStop) { return; } ASN1Element[] dataElements = new ASN1Element[] { new ASN1OctetString(statTracker.getClientID()), new ASN1OctetString(statTracker.getThreadID()), new ASN1OctetString(statTracker.getDisplayName()), new ASN1Integer(intervalNumber), new ASN1Enumerated(Constants.STAT_REPORT_TYPE_ADD), new ASN1OctetString(String.valueOf(statValue)) }; currentStatList.add(dataElements); } /** * Indicates that the provided value should be averaged with the corresponding * values for all threads by all clients for this statistic and interval. * * @param statTracker The stat tracker reporting the statistic. * @param intervalNumber The interval number for this value. * @param statValue the value being reported. */ public void reportStatToAverage(StatTracker statTracker, int intervalNumber, double statValue) { if (shouldStop) { return; } ASN1Element[] dataElements = new ASN1Element[] { new ASN1OctetString(statTracker.getClientID()), new ASN1OctetString(statTracker.getThreadID()), new ASN1OctetString(statTracker.getDisplayName()), new ASN1Integer(intervalNumber), new ASN1Enumerated(Constants.STAT_REPORT_TYPE_AVERAGE), new ASN1OctetString(String.valueOf(statValue)) }; currentStatList.add(dataElements); } /** * Indicates that the stat tracker will not be providing any more data. * * @param statTracker The stat tracker that is done reporting. * @param intervalNumber The interval number for this message. */ public void doneReporting(StatTracker statTracker, int intervalNumber) { if (shouldStop) { return; } ASN1Element[] dataElements = new ASN1Element[] { new ASN1OctetString(statTracker.getClientID()), new ASN1OctetString(statTracker.getThreadID()), new ASN1OctetString(statTracker.getDisplayName()), new ASN1Integer(intervalNumber), new ASN1Enumerated(Constants.STAT_REPORT_TYPE_DONE), }; currentStatList.add(dataElements); } /** * Sends the provided message to the SLAMD server. * * @param message The message to be sent. */ private void sendMessage(Message message) { try { writer.writeElement(message.encode()); } catch (IOException ioe) { System.err.println("Unable to send stat reporting message to the SLAMD " + "server: " + ioe); shouldStop = true; } } /** * Sends the provided message to the SLAMD server and reads the corresponding * response. * * @param message The message to be sent. * * @return The response received for the specified message, or * <CODE>null</CODE> if a problem occurred or the server is shutting * down. */ private Message sendMessageAndReadResponse(Message message) { try { writer.writeElement(message.encode()); } catch (IOException ioe) { System.err.println("Unable to send stat reporting message to the SLAMD " + "server: " + ioe); shouldStop = true; return null; } try { while (! shouldStop) { Message respMsg = Message.decode( reader.readElement(Constants.MAX_BLOCKING_READ_TIME)); if (respMsg.getMessageID() == message.getMessageID()) { return respMsg; } else { // This is not good. We got a message that doesn't correspond to our // message ID. Look at it briefly to see if it's a server shutdown // message, and if not, then just throw it away. Is there anything // else we can do about it? if (respMsg instanceof ServerShutdownMessage) { shouldStop = true; return null; } } } // If we've gotten here, then we mush be shutting down. return null; } catch (Exception e) { System.err.println("Unable to read stat reporting response from the " + "SLAMD server: " + e); shouldStop = true; return null; } } /** * Retrieves the message ID that should be used in the next message sent to * the SLAMD server. * * @return The message ID that should be used in the next message sent to the * SLAMD server. */ private int nextMessageID() { int returnID = nextMessageID; nextMessageID += 2; return returnID; } /** * Loops repeatedly waiting for new messages to send to the SLAMD server. */ public void run() { while (! shouldStop) { long stopSleepTime = System.currentTimeMillis() + (1000 * reportInterval); Vector<ASN1Element[]> list = currentStatList; currentStatList = standbyStatList; if (list.isEmpty()) { standbyStatList = list; } else { ASN1Sequence[] dataSequences = new ASN1Sequence[list.size()]; for (int i=0; i < dataSequences.length; i++) { dataSequences[i] = new ASN1Sequence(list.get(i)); } sendMessage(new ReportStatisticMessage(nextMessageID(), jobID, dataSequences)); // Clear the list and make it the new standby list.clear(); standbyStatList = list; } // Sleep before checking again. long sleepTime = stopSleepTime - System.currentTimeMillis(); if (sleepTime > 0) { try { Thread.sleep(sleepTime); } catch (InterruptedException ie) {} } } } /** * Indicates that this stat reporter should stop running. */ public void stopRunning() { shouldStop = true; } }