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.dbcp;
19  
20  
21  import java.sql.Connection;
22  import java.sql.Driver;
23  import java.sql.DriverManager;
24  import java.sql.Statement;
25  import java.util.Properties;
26  import java.util.logging.Logger;
27  import javax.sql.DataSource;
28  import org.apache.commons.dbcp.AbandonedConfig;
29  import org.apache.commons.dbcp.AbandonedObjectPool;
30  import org.apache.commons.dbcp.BasicDataSource;
31  import org.apache.commons.dbcp.ConnectionFactory;
32  import org.apache.commons.dbcp.DriverConnectionFactory;
33  import org.apache.commons.dbcp.DriverManagerConnectionFactory;
34  import org.apache.commons.dbcp.PoolableConnectionFactory;
35  import org.apache.commons.dbcp.PoolingDataSource;
36  import org.apache.commons.pool.KeyedObjectPoolFactory;
37  import org.apache.commons.pool.PoolableObjectFactory;
38  import org.apache.commons.pool.impl.GenericKeyedObjectPool;
39  import org.apache.commons.pool.impl.GenericKeyedObjectPoolFactory;
40  import org.apache.commons.pool.impl.GenericObjectPool;
41  import org.apache.commons.math.random.RandomData;
42  import org.apache.commons.math.random.RandomDataImpl;
43  import org.apache.commons.performance.ConfigurationException;
44  import org.apache.commons.performance.ClientThread;
45  import org.apache.commons.performance.LoadGenerator;
46  import org.apache.commons.performance.Statistics;
47   
48  /**
49   * Configurable load / performance tester for commons dbcp.
50   * Uses Commons Digester to parse and load configuration and spawns
51   * DBCPClientThread instances to generate load and gather statistics.
52   *
53   */
54  public class DBCPSoak extends LoadGenerator {
55      // Connection properties
56      private String driverClass;
57      private String connectUrl;
58      private String connectUser;
59      private String connectPassword;
60      private String queryType;
61      
62      // Connection pool properties
63      private String poolType;
64      private String driverType;
65      private String factoryType;
66      private boolean autocommit;
67      private boolean readOnly;
68      private byte exhaustedAction;
69      private boolean testOnBorrow;
70      private boolean testOnReturn;
71      private long timeBetweenEvictions;
72      private int testsPerEviction;
73      private long idleTimeout;
74      private boolean testWhileIdle;
75      private String validationQuery;
76      private AbandonedConfig abandonedConfig = new AbandonedConfig();
77      private boolean poolPreparedStatements;
78      private int maxOpenStatements;
79      private int maxActive;
80      private int maxIdle;
81      private int minIdle;
82      private long maxWait;
83      
84      // DataSource type
85      private String dataSourceType;
86      
87      // Instance variables
88      private GenericObjectPool connectionPool;
89      private DataSource dataSource;
90      
91      /**
92       * Create connection pool and, if necessary, test table.
93       */
94      protected void init() throws Exception {
95          
96          if (dataSourceType.equals("BasicDataSource")) {
97              BasicDataSource bds = new BasicDataSource();
98              bds.setDefaultAutoCommit(autocommit);
99              bds.setPassword(connectPassword);
100             bds.setUrl(connectUrl);
101             bds.setUsername(connectUser);
102             bds.setDriverClassName(driverClass);
103             bds.setMinEvictableIdleTimeMillis(idleTimeout);
104             bds.setMaxActive(maxActive);
105             bds.setMaxIdle(maxIdle);
106             bds.setMaxWait(maxWait);
107             bds.setMinIdle(minIdle);
108             bds.setPoolPreparedStatements(poolPreparedStatements);
109             bds.setDefaultReadOnly(readOnly);
110             bds.setTestOnBorrow(testOnBorrow);
111             bds.setTestOnReturn(testOnReturn);
112             bds.setTestWhileIdle(testWhileIdle);
113             bds.setNumTestsPerEvictionRun(testsPerEviction);
114             bds.setTimeBetweenEvictionRunsMillis(timeBetweenEvictions);
115             bds.setValidationQuery(validationQuery);
116             if (poolType.equals("AbandonedObjectPool")) {
117                     bds.setRemoveAbandoned(true);
118             }
119             dataSource = bds; 
120             checkDatabase();
121             return;
122         }
123 
124         Class.forName(driverClass);
125         
126         // Create object pool
127         if (poolType.equals("GenericObjectPool")) {
128             connectionPool = new GenericObjectPool(
129                     null, maxActive, exhaustedAction,
130                     maxWait, maxIdle, minIdle, testOnBorrow, testOnReturn,
131                     timeBetweenEvictions, testsPerEviction, idleTimeout,
132                     testWhileIdle);
133         } else if (poolType.equals("AbandonedObjectPool")) {
134             connectionPool = new AbandonedObjectPool(null,abandonedConfig);
135         } else {
136             throw new ConfigurationException(
137                     "invalid pool type configuration: " + poolType);
138         }
139         
140         // Create raw connection factory
141         ConnectionFactory connectionFactory = null;
142         if (driverType.equals("DriverManager")) {
143             connectionFactory = new DriverManagerConnectionFactory(
144                     connectUrl,connectUser,
145                     connectPassword);
146         } else if (driverType.equals("Driver")) {
147             Properties props = new Properties();
148             props.put("user", connectUser);
149             props.put("password", connectPassword);
150             connectionFactory = new DriverConnectionFactory(
151                     (Driver) Class.forName(driverClass).newInstance(),
152                     connectUrl, props);
153         } else {
154             throw new ConfigurationException(
155             "Bad config setting for driver type"); 
156         } 
157    
158         // Create object factory
159         PoolableObjectFactory poolableConnectionFactory = null;
160         KeyedObjectPoolFactory statementPoolFactory = null;
161         if (poolPreparedStatements) { // Use same defaults as BasicDataSource
162             statementPoolFactory = new GenericKeyedObjectPoolFactory(null,
163                         -1, // unlimited maxActive (per key)
164                         GenericKeyedObjectPool.WHEN_EXHAUSTED_FAIL,
165                         0, // maxWait
166                         1, // maxIdle (per key)
167                         maxOpenStatements); //TODO: make all configurable
168         }
169         if (factoryType.equals("PoolableConnectionFactory")) {
170             poolableConnectionFactory = 
171                 new PoolableConnectionFactory(
172                         connectionFactory,connectionPool, statementPoolFactory,
173                         validationQuery, readOnly, autocommit);
174         } else if (factoryType.equals("CPDSConnectionFactory")) {
175             throw new ConfigurationException(
176             "CPDSConnectionFactory not implemented yet");
177         } else {
178             throw new ConfigurationException(
179                     "Invalid factory type: " + factoryType);
180         }
181         
182         // Create DataSource
183         dataSource = new PoolingDataSource(connectionPool); 
184         checkDatabase();   
185     }
186     
187     protected void checkDatabase() throws Exception {
188         // Try to connect and query test_table. If "test_table" appears in 
189         // exception message, assume table needs to be created. 
190         Connection conn = dataSource.getConnection();
191         try {
192             Statement stmnt = conn.createStatement();
193             stmnt.execute("select * from test_table where indexed=1;");
194             stmnt.close();
195         } catch (Exception ex) {
196             if (ex.getMessage().indexOf("test_table") > 0) {
197                 logger.info("Creating test_table");
198                 makeTable();
199                 logger.info("test_table created successfully");
200             } else {
201                 throw ex;
202             }
203         } finally {
204             conn.close();
205         }
206     }
207     
208     /**
209      * Close connection pool
210      */
211     protected void cleanUp() throws Exception {
212         if (dataSourceType.equals("BasicDataSource")) {
213             ((BasicDataSource) dataSource).close();
214         } else {
215             connectionPool.close();
216         }
217     }
218     
219     protected ClientThread makeClientThread(
220             long iterations, long minDelay, long maxDelay, double sigma,
221             String delayType, long rampPeriod, long peakPeriod,
222             long troughPeriod, String cycleType, String rampType,
223             Logger logger, Statistics stats) {
224         
225         return new DBCPClientThread(iterations, minDelay, maxDelay,
226             sigma, delayType, queryType, rampPeriod, peakPeriod, 
227             troughPeriod, cycleType, rampType, logger, dataSource,
228             stats);
229     }
230     
231     // ------------------------------------------------------------------------
232     // Configuration methods specific to this LoadGenerator invoked by Digester
233     // when superclass execute calls digester.parse.
234     // ------------------------------------------------------------------------
235     public void configureDataBase(String driver, String url,
236             String username, String password, String queryType) {
237         this.driverClass = driver;
238         this.connectUrl = url;
239         this.connectUser = username;
240         this.connectPassword = password;
241         this.queryType = queryType;
242     }
243     
244     public void configureDataSource(String type) {
245         this.dataSourceType = type;
246     }
247     
248     public void configureConnectionFactory(String type,
249             String autoCommit, String readOnly, String validationQuery) {
250         this.driverType = type;
251         this.autocommit = Boolean.parseBoolean(autoCommit);
252         this.readOnly = Boolean.parseBoolean(readOnly);
253         this.validationQuery = validationQuery;
254     }
255     
256     public void configurePoolableConnectionFactory(String type, 
257             String poolPreparedStatements, String maxOpenStatements) {
258         this.factoryType = type;
259         this.poolPreparedStatements = 
260             Boolean.parseBoolean(poolPreparedStatements);
261         this.maxOpenStatements = Integer.parseInt(maxOpenStatements);
262     }
263     
264     public void configurePool(String maxActive, String maxIdle, String minIdle,
265             String maxWait, String exhaustedAction, String testOnBorrow,
266             String testOnReturn, String timeBetweenEvictions,
267             String testsPerEviction, String idleTimeout, 
268             String testWhileIdle, String type) throws ConfigurationException { 
269         this.maxActive = Integer.parseInt(maxActive);
270         this.maxIdle = Integer.parseInt(maxIdle);
271         this.minIdle = Integer.parseInt(minIdle);
272         this.maxWait = Long.parseLong(maxWait);
273         this.testOnBorrow = Boolean.parseBoolean(testOnBorrow);
274         this.testOnReturn = Boolean.parseBoolean(testOnReturn);
275         this.timeBetweenEvictions = Long.parseLong(timeBetweenEvictions);
276         this.testsPerEviction = Integer.parseInt(testsPerEviction);
277         this.idleTimeout = Long.parseLong(idleTimeout);
278         this.testWhileIdle = Boolean.parseBoolean(testWhileIdle);
279         this.poolType = type;
280         if (exhaustedAction.equals("block")) {
281             this.exhaustedAction = GenericObjectPool.WHEN_EXHAUSTED_BLOCK;
282         } else if (exhaustedAction.equals("fail")) {
283             this.exhaustedAction = GenericObjectPool.WHEN_EXHAUSTED_FAIL;
284         } else if (exhaustedAction.equals("grow")) {
285             this.exhaustedAction = GenericObjectPool.WHEN_EXHAUSTED_GROW;
286         } else { 
287             throw new ConfigurationException(
288             "Bad configuration setting for exhausted action: "
289                     + exhaustedAction); 
290         }  
291     }
292     
293     public void configureAbandonedConfig(String logAbandoned,
294             String removeAbandoned, String abandonedTimeout) {
295         abandonedConfig.setLogAbandoned(Boolean.parseBoolean(logAbandoned));
296         abandonedConfig.setRemoveAbandoned(
297                 Boolean.parseBoolean(removeAbandoned));
298         abandonedConfig.setRemoveAbandonedTimeout(
299                 Integer.parseInt(abandonedTimeout));
300     }
301      
302     /**
303      * Creates and populates the test table (test_table) used in the 
304      * load tests. The created table will have 10,000 rows and 3 columns,
305      * populated with random data: <ul>
306      * <li> indexed (indexed integer column) </li>
307      * <li> not_indexed (non-indexed integer column) </li>
308      * <li> text (non-indexed varchar column) </li>
309      * </ul>
310      *
311      * @throws Exception
312      */
313     protected void makeTable() throws Exception {
314         Class.forName(driverClass); 
315         Connection db = DriverManager.getConnection(connectUrl,connectUser,
316                 connectPassword);
317         try {
318             Statement sql = db.createStatement();
319             String sqlText = 
320                 "create table test_table (indexed int, text varchar(20)," + 
321                 " not_indexed int)";
322             sql.executeUpdate(sqlText);
323             sqlText = "CREATE INDEX test1_id_index ON test_table (indexed);";
324             sql.executeUpdate(sqlText);
325             RandomData randomData = new RandomDataImpl();
326             for (int i = 0; i < 10000; i++) {
327                 int indexed = randomData.nextInt(0, 100);
328                 int not_indexed = randomData.nextInt(0, 1000);
329                 String text = randomData.nextHexString(20);
330                 sqlText = 
331                     "INSERT INTO test_table (indexed, text, not_indexed)" + 
332                     "VALUES (" + indexed + "," + "'"+ text + "'," + 
333                     not_indexed + ");";
334                 sql.executeUpdate(sqlText);
335             }
336             sql.close();
337         } finally {
338             db.close();
339         }
340     }
341     
342     /**
343      * Add dbcp configuration to parameters loaded by super.
344      * Also set config file name.
345      */
346     protected void configure() throws Exception {
347         
348         super.configure();
349         
350         digester.addCallMethod("configuration/database",
351                 "configureDataBase", 5);
352         digester.addCallParam("configuration/database/driver", 0);
353         digester.addCallParam("configuration/database/url", 1);
354         digester.addCallParam("configuration/database/username", 2);
355         digester.addCallParam("configuration/database/password", 3);
356         digester.addCallParam("configuration/database/query-type", 4);
357         
358         digester.addCallMethod("configuration", "configureDataSource", 1);
359         digester.addCallParam("configuration/datasource-type", 0);
360         
361         digester.addCallMethod("configuration/connection-factory", 
362                 "configureConnectionFactory", 4);
363         digester.addCallParam(
364                 "configuration/connection-factory/type", 0);
365         digester.addCallParam(
366                 "configuration/connection-factory/auto-commit", 1);
367         digester.addCallParam(
368                 "configuration/connection-factory/read-only", 2);
369         digester.addCallParam(
370                 "configuration/connection-factory/validation-query", 3);
371         
372         digester.addCallMethod("configuration/poolable-connection-factory", 
373                 "configurePoolableConnectionFactory", 3);
374         digester.addCallParam(
375                 "configuration/poolable-connection-factory/type", 0);
376         digester.addCallParam(
377                 "configuration/poolable-connection-factory/pool-prepared-statements", 1);
378         digester.addCallParam(
379                 "configuration/poolable-connection-factory/max-open-statements", 2);
380         
381         digester.addCallMethod("configuration/pool", 
382                 "configurePool", 12);
383         digester.addCallParam(
384                 "configuration/pool/max-active", 0);
385         digester.addCallParam(
386                 "configuration/pool/max-idle", 1);
387         digester.addCallParam(
388                 "configuration/pool/min-idle", 2);
389         digester.addCallParam(
390                 "configuration/pool/max-wait", 3);
391         digester.addCallParam(
392                 "configuration/pool/exhausted-action", 4);
393         digester.addCallParam(
394                 "configuration/pool/test-on-borrow", 5);
395         digester.addCallParam(
396                 "configuration/pool/test-on-return", 6);
397         digester.addCallParam(
398                 "configuration/pool/time-between-evictions", 7);
399         digester.addCallParam(
400                 "configuration/pool/tests-per-eviction", 8);
401         digester.addCallParam(
402                 "configuration/pool/idle-timeout", 9);
403         digester.addCallParam(
404                 "configuration/pool/test-while-idle", 10);
405         digester.addCallParam(
406                 "configuration/pool/type", 11);
407         
408         digester.addCallMethod("configuration/abandoned-config",
409                 "configureAbandonedConfig", 3);
410         digester.addCallParam(
411                 "configuration/abandoned-config/log-abandoned", 0);
412         digester.addCallParam(
413                 "configuration/abandoned-config/remove-abandoned", 1);
414         digester.addCallParam(
415                 "configuration/abandoned-config/abandoned-timeout", 2);
416         
417         this.configFile = "config-dbcp.xml";
418     }
419 
420     public GenericObjectPool getConnectionPool() {
421         return connectionPool;
422     }
423 }