001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018 package org.apache.commons.performance; 019 020 import java.io.File; 021 import java.util.Iterator; 022 import java.util.concurrent.ExecutorService; 023 import java.util.concurrent.Executors; 024 import java.util.concurrent.TimeUnit; 025 import java.util.logging.Logger; 026 import org.apache.commons.digester.Digester; 027 028 /** 029 * <p>Base class for load / peformance test runners. 030 * Uses Commons Digester to parse and load configuration and spawns 031 * {@link ClientThread} instances to generate load and gather statistics.</p> 032 * 033 * <p>Subclasses <code>must</code> implement <code>makeClientThread</code> to 034 * create client thread instances to be kicked off by <code>execute.</code> 035 * Subclasses will also in general override <code>configure</code> to load 036 * additional configuration parameters and pass them on to the client in 037 * <code>makeClientThread.</code> Implementations of <code>configure</code> 038 * should start with a <code>super()</code> call so that the base configuration 039 * parameters are loaded. This method should also set the <code>configFile</code> 040 * property to a valid URI or filespec (suitable argument for Digester's parse 041 * method). Setup code that needs to be executed before any client threads are 042 * spawned should be put in <code>init</code></p> 043 * 044 * <p>See 045 * <a href="http://svn.apache.org/viewvc/commons/sandbox/performance/trunk/src/java/org/apache/commons/performance/dbcp/DBCPSoak.java?view=markup"> 046 * DBCPSoak</a> and its 047 * <a href="http://svn.apache.org/viewvc/commons/sandbox/performance/trunk/config-dbcp.xml?view=markup"> 048 * sample configuration file</a> for an example. As in that example, additional 049 * sections of the config file should be parsed and loaded in the overridden 050 * <code>configure</code> method. The "run" section is required by the base 051 * implementation. That example also illustrates how <code>init</code> 052 * can be used to set up resources or data structures required by the client 053 * threads.</p> 054 * 055 */ 056 public abstract class LoadGenerator { 057 058 /** logger */ 059 protected static final Logger logger = 060 Logger.getLogger(LoadGenerator.class.getName()); 061 062 /** Statistics aggregator */ 063 private static Statistics stats = new Statistics(); 064 065 // Client thread properties 066 protected long minDelay; 067 protected long maxDelay; 068 protected double sigma; 069 protected String delayType; 070 protected String rampType; 071 protected long rampPeriod; 072 protected long peakPeriod; 073 protected long troughPeriod; 074 protected String cycleType; 075 076 // Run properties 077 private long numClients; 078 private long iterations; 079 080 protected Digester digester = new Digester(); 081 protected String configFile = null; 082 083 /** 084 * <p>Invokes {@link #configure()} to load digester rules, then digster.parse, 085 * then {@link #init} to initialize configuration members. Then spawns and 086 * executes {@link #numClients} ClientThreads using {@link #makeClientThread} 087 * to create the ClientThread instances. Waits for all spawned threads to 088 * terminate and then logs accumulated statistics, using 089 * {@link Statistics#displayOverallSummary}</p> 090 * 091 * <p>Subclasses should not need to override this method, but must implement 092 * {@link #makeClientThread} and may override {@link #configure} and 093 * {@link #init} to prepare data to pass to <code>makeClientThread</code>, 094 * and {@link #cleanUp} to clean up after all threads terminate. 095 * </p> 096 * 097 * @throws Exception 098 */ 099 public void execute() throws Exception { 100 configure(); 101 parseConfigFile(); 102 init(); 103 // Spawn and execute client threads 104 ExecutorService ex = Executors.newFixedThreadPool((int)numClients); 105 for (int i = 0; i < numClients; i++) { 106 ClientThread clientThread = makeClientThread(iterations, minDelay, 107 maxDelay, sigma, delayType, rampPeriod, peakPeriod, 108 troughPeriod, cycleType, rampType, logger, stats); 109 ex.execute(clientThread); 110 } 111 ex.shutdown(); 112 // hard time limit of one day for now 113 // TODO: make this configurable 114 ex.awaitTermination(60 * 60 * 24, TimeUnit.SECONDS); 115 116 // Log summary statistics for accumulated metrics 117 logger.info(stats.displayOverallSummary()); 118 119 // clean up 120 cleanUp(); 121 } 122 123 protected abstract ClientThread makeClientThread( 124 long iterations, long minDelay, long maxDelay, double sigma, 125 String delayType, long rampPeriod, long peakPeriod, 126 long troughPeriod, String cycleType, String rampType, 127 Logger logger, Statistics stats); 128 129 /** 130 * This method is invoked by {@link #execute()} after {@link #configure()} 131 * and digester parse, just before client threads are created. Objects that 132 * need to be created and passed to client threads using configuration info 133 * parsed from the config file should be created in this method. 134 * 135 * @throws Exception 136 */ 137 protected void init() throws Exception {} 138 139 140 /** 141 * This method is invoked by {@link #execute()} after all spawned threads 142 * have terminated. Override to clean up any resources allocated in 143 * {@link #init()}. 144 * 145 * @throws Exception 146 */ 147 protected void cleanUp() throws Exception {} 148 149 150 /** 151 * Configures basic run parameters. Invoked by Digester via a rule defined 152 * in {@link #configure()}. 153 * 154 * @param iterations number of iterations 155 * @param clients number of client threads 156 * @param minDelay minimum delay between client thread requests (ms) 157 * @param maxDelay maximum delay between client thread requests (ms) 158 * @param sigma standard deviation of delay 159 * @param delayType type of delay (constant, gaussian, poisson) 160 * @param rampType type of ramp (none, linear, random) 161 * @param rampPeriod rampup/rampdown time 162 * @param peakPeriod peak period 163 * @param troughPeriod trough period 164 * @param cycleType cycle type (none, oscillating) 165 * @throws ConfigurationException 166 */ 167 168 public void configureRun(String iterations, String clients, 169 String minDelay, String maxDelay, String sigma, 170 String delayType, String rampType, String rampPeriod, 171 String peakPeriod, String troughPeriod, String cycleType) 172 throws ConfigurationException { 173 174 this.iterations = Long.parseLong(iterations); 175 this.numClients = Long.parseLong(clients); 176 this.minDelay = Long.parseLong(minDelay); 177 this.maxDelay = Long.parseLong(maxDelay); 178 this.sigma = Double.parseDouble(sigma); 179 this.delayType = delayType; 180 this.rampType = rampType; 181 this.rampPeriod = Long.parseLong(rampPeriod); 182 this.peakPeriod = Long.parseLong(peakPeriod); 183 this.troughPeriod = Long.parseLong(troughPeriod); 184 this.cycleType = cycleType; 185 if (cycleType.equals("oscillating") && this.rampPeriod <= 0) { 186 throw new ConfigurationException( 187 "Ramp period must be positive for oscillating cycle type"); 188 } 189 } 190 191 /** 192 * <p>Starts preparing Digester to parse the configuration file, pushing 193 * *this onto the stack and loading rules to configure basic "run" 194 * parameters. 195 * </p> 196 * <p>Subclasses can override this, using <code>super()</code> to load base 197 * parameters and then adding additional </code>addCallMethod</code> 198 * sequences for additional parameters. 199 * </p> 200 * 201 * @throws Exception 202 */ 203 protected void configure() throws Exception { 204 digester.push(this); 205 206 digester.addCallMethod("configuration/run", 207 "configureRun", 11); 208 digester.addCallParam( 209 "configuration/run/iterations", 0); 210 digester.addCallParam( 211 "configuration/run/clients", 1); 212 digester.addCallParam( 213 "configuration/run/delay-min", 2); 214 digester.addCallParam( 215 "configuration/run/delay-max", 3); 216 digester.addCallParam( 217 "configuration/run/delay-sigma", 4); 218 digester.addCallParam( 219 "configuration/run/delay-type", 5); 220 digester.addCallParam( 221 "configuration/run/ramp-type", 6); 222 digester.addCallParam( 223 "configuration/run/ramp-period", 7); 224 digester.addCallParam( 225 "configuration/run/peak-period", 8); 226 digester.addCallParam( 227 "configuration/run/trough-period", 9); 228 digester.addCallParam( 229 "configuration/run/cycle-type", 10); 230 } 231 232 protected void parseConfigFile() throws Exception { 233 // TODO: get rid of File spec 234 digester.parse(new File(configFile)); 235 } 236 237 /** 238 * @return the configFile 239 */ 240 public String getConfigFile() { 241 return configFile; 242 } 243 244 /** 245 * @param configFile the configFile to set 246 */ 247 public void setConfigFile(String configFile) { 248 this.configFile = configFile; 249 } 250 251 /** 252 * @return the digester 253 */ 254 public Digester getDigester() { 255 return digester; 256 } 257 258 /** 259 * @return statistics 260 */ 261 public Statistics getStatistics() { 262 return stats; 263 } 264 }