1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package org.apache.commons.performance; 19 20 import java.io.File; 21 import java.util.Iterator; 22 import java.util.concurrent.ExecutorService; 23 import java.util.concurrent.Executors; 24 import java.util.concurrent.TimeUnit; 25 import java.util.logging.Logger; 26 import org.apache.commons.digester.Digester; 27 28 /** 29 * <p>Base class for load / peformance test runners. 30 * Uses Commons Digester to parse and load configuration and spawns 31 * {@link ClientThread} instances to generate load and gather statistics.</p> 32 * 33 * <p>Subclasses <code>must</code> implement <code>makeClientThread</code> to 34 * create client thread instances to be kicked off by <code>execute.</code> 35 * Subclasses will also in general override <code>configure</code> to load 36 * additional configuration parameters and pass them on to the client in 37 * <code>makeClientThread.</code> Implementations of <code>configure</code> 38 * should start with a <code>super()</code> call so that the base configuration 39 * parameters are loaded. This method should also set the <code>configFile</code> 40 * property to a valid URI or filespec (suitable argument for Digester's parse 41 * method). Setup code that needs to be executed before any client threads are 42 * spawned should be put in <code>init</code></p> 43 * 44 * <p>See 45 * <a href="http://svn.apache.org/viewvc/commons/sandbox/performance/trunk/src/java/org/apache/commons/performance/dbcp/DBCPSoak.java?view=markup"> 46 * DBCPSoak</a> and its 47 * <a href="http://svn.apache.org/viewvc/commons/sandbox/performance/trunk/config-dbcp.xml?view=markup"> 48 * sample configuration file</a> for an example. As in that example, additional 49 * sections of the config file should be parsed and loaded in the overridden 50 * <code>configure</code> method. The "run" section is required by the base 51 * implementation. That example also illustrates how <code>init</code> 52 * can be used to set up resources or data structures required by the client 53 * threads.</p> 54 * 55 */ 56 public abstract class LoadGenerator { 57 58 /** logger */ 59 protected static final Logger logger = 60 Logger.getLogger(LoadGenerator.class.getName()); 61 62 /** Statistics aggregator */ 63 private static Statistics stats = new Statistics(); 64 65 // Client thread properties 66 protected long minDelay; 67 protected long maxDelay; 68 protected double sigma; 69 protected String delayType; 70 protected String rampType; 71 protected long rampPeriod; 72 protected long peakPeriod; 73 protected long troughPeriod; 74 protected String cycleType; 75 76 // Run properties 77 private long numClients; 78 private long iterations; 79 80 protected Digester digester = new Digester(); 81 protected String configFile = null; 82 83 /** 84 * <p>Invokes {@link #configure()} to load digester rules, then digster.parse, 85 * then {@link #init} to initialize configuration members. Then spawns and 86 * executes {@link #numClients} ClientThreads using {@link #makeClientThread} 87 * to create the ClientThread instances. Waits for all spawned threads to 88 * terminate and then logs accumulated statistics, using 89 * {@link Statistics#displayOverallSummary}</p> 90 * 91 * <p>Subclasses should not need to override this method, but must implement 92 * {@link #makeClientThread} and may override {@link #configure} and 93 * {@link #init} to prepare data to pass to <code>makeClientThread</code>, 94 * and {@link #cleanUp} to clean up after all threads terminate. 95 * </p> 96 * 97 * @throws Exception 98 */ 99 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 }