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