View Javadoc

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 }