/* * 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; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.util.Iterator; import java.util.Properties; import com.slamd.client.Client; import com.slamd.client.ClientException; import com.slamd.client.ClientMessageWriter; import com.slamd.common.Constants; /** * This class defines a command-line (non-GUI) application that may serve as a * client of the SLAMD server. All of the configuration is done through * command-line options. This is a good client to run as a background process. * * * @author Neil A. Wilson */ public class CommandLineClient implements ClientMessageWriter { /** * The name of the configuration property that specifies the address of the * SLAMD server. */ public static final String PROPERTY_SLAMD_ADDRESS = "SLAMD_ADDRESS"; /** * The name of the configuration property that specifies the client port for * the SLAMD server. */ public static final String PROPERTY_SLAMD_PORT = "SLAMD_LISTEN_PORT"; /** * The name of the configuration property that specifies the stat port for the * SLAMD server. */ public static final String PROPERTY_SLAMD_STAT_PORT = "SLAMD_STAT_PORT"; /** * The name of the configuration property that specifies the client address. */ public static final String PROPERTY_CLIENT_ADDRESS = "CLIENT_ADDRESS"; /** * The name of the configuration property that specifies the client ID. */ public static final String PROPERTY_CLIENT_ID = "CLIENT_ID"; /** * The name of the configuration property that specifies whether to enable * real-time statistics tracking. */ public static final String PROPERTY_ENABLE_RT = "ENABLE_REAL_TIME_STATS"; /** * The name of the configuration property that specifies the interval for * reporting real-time statistics. */ public static final String PROPERTY_RT_INTERVAL = "REAL_TIME_REPORT_INTERVAL"; /** * The name of the configuration property that specifies whether to enable * stat persistence. */ public static final String PROPERTY_ENABLE_PERSISTENCE = "ENABLE_STAT_PERSISTENCE"; /** * The name of the configuration property that specifies the directory for * stat persistence data. */ public static final String PROPERTY_PERSISTENCE_DIR = "STAT_PERSISTENCE_DIRECTORY"; /** * The name of the configuration property that specifies the stat persistence * interval. */ public static final String PROPERTY_PERSISTENCE_INTERVAL = "STAT_PERSISTENCE_INTERVAL"; /** * The name of the configuration property that specifies the authentication * ID. */ public static final String PROPERTY_AUTH_ID = "AUTH_ID"; /** * The name of the configuration property that specifies the authentication * password. */ public static final String PROPERTY_AUTH_PW = "AUTH_PASS"; /** * The name of the configuration property that specifies whether to use SSL. */ public static final String PROPERTY_USE_SSL = "USE_SSL"; /** * The name of the configuration property that specifies whether to blindly * trust any certificate. */ public static final String PROPERTY_BLIND_TRUST = "BLIND_TRUST"; /** * The name of the configuration property that specifies the path to the SSL * keystore. */ public static final String PROPERTY_KEY_STORE = "SSL_KEY_STORE"; /** * The name of the configuration property that specifies the password for the * SSL keystore. */ public static final String PROPERTY_KEY_PASS = "SSL_KEY_PASS"; /** * The name of the configuration property that specifies the path to the SSL * trust store. */ public static final String PROPERTY_TRUST_STORE = "SSL_TRUST_PASS"; /** * The name of the configuration property that specifies the password for the * SSL trust store. */ public static final String PROPERTY_TRUST_PASS = "SSL_TRUST_PASS"; /** * The name of the configuration property that specifies whether to aggregate * client thread data. */ public static final String PROPERTY_AGGREGATE = "AGGREGATE_CLIENT_THREADS"; /** * The name of the configuration property that specifies whether to operate in * restricted mode. */ public static final String PROPERTY_RESTRICTED_MODE = "RESTRICTED_MODE"; /** * The name of the configuration property that specifies whether to disable * the custom class loader. */ public static final String PROPERTY_DISABLE_CL = "DISABLE_CUSTOM_CLASS_LOADER"; /** * The name of the configuration property that specifies whether to enable * verbose mode. */ public static final String PROPERTY_VERBOSE = "VERBOSE_MODE"; /** * The name of the configuration property that specifies whether to enable * quiet mode. */ public static final String PROPERTY_QUIET = "QUIET_MODE"; /** * The name of the configuration property that specifies the path to the log * file. */ public static final String PROPERTY_LOG_FILE = "LOG_FILE"; // Indicates whether the data collected by the individual client threads // should be aggregated before being sent back to the SLAMD server. private boolean aggregateThreadData = false; // Indicates whether the client should blindly trust any SSL certificate // presented by the SLAMD server. private boolean blindTrust = false; // Indicates whether the client should enable real-time statistics collection. private boolean enableRealTimeStats = false; // Indicates whether the client should try to persist statistical data. private boolean persistStats; // Indicates whether to operate in quiet mode (no output while the client is // running). private boolean quietMode = false; // Indicates whether the client should operate in restricted mode. private boolean restrictedMode = false; // Indicates whether to print verbose messages. private boolean verboseMode = false; // Indicates whether to use the custom class loader when loading job classes. private boolean useCustomClassLoader = true; // Indicates whether the client should use SSL when communicating with the // SLAMD server. private boolean useSSL = false; // Indicates whether to enable automatic time synchronization with the SLAMD // server. private boolean useTimeSync = true; // The interval in seconds between saves of persistent statistical data. private int persistenceInterval = Constants.DEFAULT_STAT_PERSISTENCE_INTERVAL; // The port number to use when connecting to the SLAMD server. private int slamdServerPort = Constants.DEFAULT_LISTENER_PORT_NUMBER; // The port number to use for reporting stats to the SLAMD server. private int slamdStatPort = Constants.DEFAULT_STAT_LISTENER_PORT_NUMBER; // The interval in seconds to use when reporting stats to the server. private int statReportInterval = Constants.DEFAULT_STAT_REPORT_INTERVAL; // The client code that actually performs all the work of interacting with // the SLAMD server. private Client client; // The print writer that will be used to write log messages. private PrintWriter logWriter = null; // The ID that this client uses to authenticate to the SLAMD server. private String authenticationID = null; // The credentials for the provided authentication ID. private String authenticationCredentials = null; // The name of the directory to which class files may be written. private String classPath = null; // The source address to use for the client. private String clientAddress = null; // The client ID to use for the client. private String clientID; // The path to the log file to which output should be written. private String logFile = null; // The path to the directory into which the stat persistence data will be // written. private String persistenceDirectory = "statpersistence"; // The hostname or IP address of the SLAMD server. private String slamdServerAddress = "127.0.0.1"; // The location of the JSSE key store. private String sslKeyStore = null; // The password to use to access the JSSE key store. private String sslKeyStorePassword = null; // The location of the JSSE trust store. private String sslTrustStore = null; // The password to use to access the JSSE trust store. private String sslTrustStorePassword = null; /** * Passes off all the work to the constructor so that we can pass in a * reference to this class to the client. * * @param args The command-line arguments provided to this application. */ public static void main(String[] args) { new CommandLineClient(args); } /** * Parses the command line parameters and connects to the SLAMD server to * accept and process job requests. * * @param args The command-line arguments provided to this application. */ public CommandLineClient(String[] args) { // See if a configuration file was specified. If so, then use it to // initialize the client settings. for (int i=0; i < args.length; i++) { if (args[i].equals("-f")) { processConfigFile(args[++i]); break; } } // Process the command line arguments to override anything that might have // been specified in the configuration. for (int i=0; i < args.length; i++) { if (args[i].equals("-h")) { slamdServerAddress = args[++i]; } else if (args[i].equals("-C")) { clientAddress = args[++i]; } else if (args[i].equals("-n")) { clientID = args[++i]; } else if (args[i].equals("-p")) { slamdServerPort = Integer.parseInt(args[++i]); } else if (args[i].equals("-P")) { slamdStatPort = Integer.parseInt(args[++i]); } else if (args[i].equals("-D")) { authenticationID = args[++i]; } else if (args[i].equals("-w")) { authenticationCredentials = args[++i]; } else if (args[i].equals("-c")) { classPath = args[++i]; } else if (args[i].equals("-a")) { aggregateThreadData = true; } else if (args[i].equals("-R")) { restrictedMode = true; } else if (args[i].equals("-S")) { useSSL = true; } else if (args[i].equals("-s")) { enableRealTimeStats = true; } else if (args[i].equals("-I")) { statReportInterval = Integer.parseInt(args[++i]); } else if (args[i].equals("-r")) { persistStats = true; } else if (args[i].equals("-i")) { persistenceInterval = Integer.parseInt(args[++i]); } else if (args[i].equals("-d")) { persistenceDirectory = args[++i]; } else if (args[i].equals("-B")) { blindTrust = true; } else if (args[i].equals("-k")) { sslKeyStore = args[++i]; } else if (args[i].equals("-K")) { sslKeyStorePassword = args[++i]; } else if (args[i].equals("-t")) { sslTrustStore = args[++i]; } else if (args[i].equals("-T")) { sslTrustStorePassword = args[++i]; } else if (args[i].equals("-l")) { logFile = args[++i]; } else if (args[i].equals("-L")) { useCustomClassLoader = false; } else if (args[i].equals("-Y")) { useTimeSync = false; } else if (args[i].equals("-v")) { verboseMode = true; quietMode = false; } else if (args[i].equals("-q")) { quietMode = true; verboseMode = false; } else if (args[i].equals("-H")) { displayUsage(); System.exit(0); } else if (args[i].equals("-f")) { // Already handled this. i++; } else { System.err.println("ERROR: Unrecognized option \"" + args[i] + '"'); displayUsage(); System.exit(1); } } // If a log file has been requested, open the appropriate writer. if ((logFile == null) || (logFile.length() == 0)) { logWriter = null; } else { try { logWriter = new PrintWriter(new FileWriter(logFile)); } catch (IOException ioe) { System.err.println("ERROR: Could not open output file \"" + logFile + "\" -- " + ioe); System.exit(1); } } // Create the client and let it do all the work. try { int authType; if ((authenticationID == null) || (authenticationCredentials == null)) { authType = Constants.AUTH_TYPE_NONE; } else { authType = Constants.AUTH_TYPE_SIMPLE; } client = new Client(clientID, clientAddress, slamdServerAddress, slamdServerPort, slamdStatPort, useTimeSync, enableRealTimeStats, statReportInterval, persistStats, persistenceDirectory, persistenceInterval, authType, authenticationID, authenticationCredentials, restrictedMode, useCustomClassLoader, classPath, useSSL, blindTrust, sslKeyStore, sslKeyStorePassword, sslTrustStore, sslTrustStorePassword, this); client.aggregateThreadData(aggregateThreadData); client.start(); } catch (ClientException sce) { sce.printStackTrace(); } } /** * Processes the contents of the specified config file. * * @param configFile The path to the configuration file to process. */ public void processConfigFile(String configFile) { Properties properties = new Properties(); try { properties.load(new FileInputStream(configFile)); } catch (IOException ioe) { System.err.println("ERROR: Unable to load properties file \"" + configFile + "\": " + ioe); System.exit(1); } Iterator keys = properties.keySet().iterator(); while (keys.hasNext()) { String name = (String) keys.next(); String value = properties.getProperty(name, null); if ((value == null) || (value.length() == 0)) { continue; } if (name.equals(PROPERTY_SLAMD_ADDRESS)) { slamdServerAddress = value; } else if (name.equals(PROPERTY_SLAMD_PORT)) { slamdServerPort = Integer.parseInt(value); } else if (name.equals(PROPERTY_SLAMD_STAT_PORT)) { slamdStatPort = Integer.parseInt(value); } else if (name.equals(PROPERTY_CLIENT_ADDRESS)) { clientAddress = value; } else if (name.equals(PROPERTY_CLIENT_ID)) { clientID = value; } else if (name.equals(PROPERTY_ENABLE_RT)) { if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("yes") || value.equalsIgnoreCase("on") || value.equalsIgnoreCase("1")) { enableRealTimeStats = true; } else if (value.equalsIgnoreCase("false") || value.equalsIgnoreCase("no") || value.equalsIgnoreCase("off") || value.equalsIgnoreCase("0")) { enableRealTimeStats = false; } else { System.err.println("ERROR: Cannot interpret the value of the " + PROPERTY_ENABLE_RT + " property as a Boolean."); System.exit(1); } } else if (name.equals(PROPERTY_RT_INTERVAL)) { statReportInterval = Integer.parseInt(value); } else if (name.equals(PROPERTY_ENABLE_PERSISTENCE)) { if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("yes") || value.equalsIgnoreCase("on") || value.equalsIgnoreCase("1")) { persistStats = true; } else if (value.equalsIgnoreCase("false") || value.equalsIgnoreCase("no") || value.equalsIgnoreCase("off") || value.equalsIgnoreCase("0")) { persistStats = false; } else { System.err.println("ERROR: Cannot interpret the value of the " + PROPERTY_ENABLE_PERSISTENCE + " property as a Boolean."); System.exit(1); } } else if (name.equals(PROPERTY_PERSISTENCE_DIR)) { persistenceDirectory = value; } else if (name.equals(PROPERTY_PERSISTENCE_INTERVAL)) { persistenceInterval = Integer.parseInt(value); } else if (name.equals(PROPERTY_AUTH_ID)) { authenticationID = value; } else if (name.equals(PROPERTY_AUTH_PW)) { authenticationCredentials = value; } else if (name.equals(PROPERTY_USE_SSL)) { if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("yes") || value.equalsIgnoreCase("on") || value.equalsIgnoreCase("1")) { useSSL = true; } else if (value.equalsIgnoreCase("false") || value.equalsIgnoreCase("no") || value.equalsIgnoreCase("off") || value.equalsIgnoreCase("0")) { useSSL = false; } else { System.err.println("ERROR: Cannot interpret the value of the " + PROPERTY_USE_SSL + " property as a Boolean."); System.exit(1); } } else if (name.equals(PROPERTY_BLIND_TRUST)) { if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("yes") || value.equalsIgnoreCase("on") || value.equalsIgnoreCase("1")) { blindTrust = true; } else if (value.equalsIgnoreCase("false") || value.equalsIgnoreCase("no") || value.equalsIgnoreCase("off") || value.equalsIgnoreCase("0")) { blindTrust = false; } else { System.err.println("ERROR: Cannot interpret the value of the " + PROPERTY_BLIND_TRUST + " property as a Boolean."); System.exit(1); } } else if (name.equals(PROPERTY_KEY_STORE)) { sslKeyStore = value; } else if (name.equals(PROPERTY_KEY_PASS)) { sslKeyStorePassword = value; } else if (name.equals(PROPERTY_TRUST_STORE)) { sslTrustStore = value; } else if (name.equals(PROPERTY_TRUST_PASS)) { sslTrustStorePassword = value; } else if (name.equals(PROPERTY_AGGREGATE)) { if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("yes") || value.equalsIgnoreCase("on") || value.equalsIgnoreCase("1")) { aggregateThreadData = true; } else if (value.equalsIgnoreCase("false") || value.equalsIgnoreCase("no") || value.equalsIgnoreCase("off") || value.equalsIgnoreCase("0")) { aggregateThreadData = false; } else { System.err.println("ERROR: Cannot interpret the value of the " + PROPERTY_AGGREGATE + " property as a Boolean."); System.exit(1); } } else if (name.equals(PROPERTY_RESTRICTED_MODE)) { if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("yes") || value.equalsIgnoreCase("on") || value.equalsIgnoreCase("1")) { restrictedMode = true; } else if (value.equalsIgnoreCase("false") || value.equalsIgnoreCase("no") || value.equalsIgnoreCase("off") || value.equalsIgnoreCase("0")) { restrictedMode = false; } else { System.err.println("ERROR: Cannot interpret the value of the " + PROPERTY_RESTRICTED_MODE + " property as a Boolean."); System.exit(1); } } else if (name.equals(PROPERTY_DISABLE_CL)) { if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("yes") || value.equalsIgnoreCase("on") || value.equalsIgnoreCase("1")) { useCustomClassLoader = false; } else if (value.equalsIgnoreCase("false") || value.equalsIgnoreCase("no") || value.equalsIgnoreCase("off") || value.equalsIgnoreCase("0")) { useCustomClassLoader = true; } else { System.err.println("ERROR: Cannot interpret the value of the " + PROPERTY_DISABLE_CL + " property as a Boolean."); System.exit(1); } } else if (name.equals(PROPERTY_VERBOSE)) { if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("yes") || value.equalsIgnoreCase("on") || value.equalsIgnoreCase("1")) { verboseMode = true; } else if (value.equalsIgnoreCase("false") || value.equalsIgnoreCase("no") || value.equalsIgnoreCase("off") || value.equalsIgnoreCase("0")) { verboseMode = false; } else { System.err.println("ERROR: Cannot interpret the value of the " + PROPERTY_VERBOSE + " property as a Boolean."); System.exit(1); } } else if (name.equals(PROPERTY_QUIET)) { if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("yes") || value.equalsIgnoreCase("on") || value.equalsIgnoreCase("1")) { quietMode = true; } else if (value.equalsIgnoreCase("false") || value.equalsIgnoreCase("no") || value.equalsIgnoreCase("off") || value.equalsIgnoreCase("0")) { quietMode = false; } else { System.err.println("ERROR: Cannot interpret the value of the " + PROPERTY_QUIET + " property as a Boolean."); System.exit(1); } } else if (name.equals(PROPERTY_LOG_FILE)) { logFile = value; } } } /** * Displays usage information for this program. */ public void displayUsage() { String eol = Constants.EOL; System.err.println( "USAGE: java " + getClass().getName() + " [options]" + eol + " where [options] include:" + eol + "-f {file} -- The path to the client configuration file." + eol + "-h {host} -- The address of the SLAMD server." + eol + "-p {port} -- The port number of the SLAMD server." + eol + "-P {port} -- The port number that the SLAMD server uses for " + eol + " collecting real-time statistics. " + eol + "-C {addr} -- The local source address to use for the client." + eol + "-n {id} -- The client ID to use for the client." + eol + "-D {authid} -- The ID to use to authenticate to the SLAMD server." + eol + "-w {authpw} -- The password for the authentication ID." + eol + "-c {dir} -- The name of the directory in which Java class files" + eol + " may be written." + eol + "-a -- Indicates that the data from each thread should be" + eol + " aggregated before sending results to the server." + eol + "-R -- Indicates that the client should operate in " + eol + " restricted mode." + eol + "-S -- Indicates that the client should communicate with the" + eol + " SLAMD server over SSL." + eol + "-s -- Indicates that the client should enable real-time " + eol + " statistics reporting to the SLAMD server." + eol + "-I {value} -- Specifies the interval (in seconds) to use when " + eol + " reporting real-time stats to the SLAMD server." + eol + "-r -- Indicates that the client should retain persistent" + eol + " data on disk for recovery in case of a failure" + eol + "-i {value} -- Specifies the interval in seconds that should be used" + eol + " when periodically persisting statistics to disk" + eol + "-d {dir} -- Specifies the directory into which statistical data" + eol + " should be written if persistence is enabled" + eol + "-B -- Indicates that the client blindly trust any SSL" + eol + " certificate presented by the SLAMD server." + eol + "-k {file} -- The location of the JSSE key store." + eol + "-K {pw} -- The password needed to access the JSSE key store." + eol + "-t {file} -- The location of the JSSE trust store." + eol + "-T {pw} -- The password needed to access the JSSE trust store." + eol + "-l {file} -- The path to the output file to use rather than " + eol + " standard output." + eol + "-L -- Disable the custom class loader." + eol + "-Y -- Disable time synchronization with the SLAMD server." + eol + "-v -- Operate in verbose mode." + eol + "-q -- Operate in quiet mode." + eol + "-H -- Show this usage information." + eol ); } /** * Writes the specified message to standard output. * * @param message The message to be written. */ public void writeMessage(String message) { if (! quietMode) { if (logWriter == null) { System.out.println(message); } else { logWriter.println(message); logWriter.flush(); } } } /** * Writes the specified message to standard output if verbose mode is enabled. * * @param message The message to be written. */ public void writeVerbose(String message) { if (verboseMode) { if (logWriter == null) { System.out.println(message); } else { logWriter.println(message); logWriter.flush(); } } } /** * Indicates whether the message writer is using verbose mode and therefore * will display messages written with the <CODE>writeVerbose</CODE> method. * * @return <CODE>true</CODE> if the message writer is using verbose mode, or * <CODE>false</CODE> if not. */ public boolean usingVerboseMode() { return verboseMode; } }