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 }