001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.apache.commons.performance.dbcp;
019
020
021 import java.sql.Connection;
022 import java.sql.Driver;
023 import java.sql.DriverManager;
024 import java.sql.Statement;
025 import java.util.Properties;
026 import java.util.logging.Logger;
027 import org.apache.commons.dbcp.AbandonedConfig;
028 import org.apache.commons.dbcp.AbandonedObjectPool;
029 import org.apache.commons.dbcp.ConnectionFactory;
030 import org.apache.commons.dbcp.DriverConnectionFactory;
031 import org.apache.commons.dbcp.DriverManagerConnectionFactory;
032 import org.apache.commons.dbcp.PoolableConnectionFactory;
033 import org.apache.commons.dbcp.PoolingDataSource;
034 import org.apache.commons.pool.KeyedObjectPoolFactory;
035 import org.apache.commons.pool.PoolableObjectFactory;
036 import org.apache.commons.pool.impl.GenericKeyedObjectPool;
037 import org.apache.commons.pool.impl.GenericKeyedObjectPoolFactory;
038 import org.apache.commons.pool.impl.GenericObjectPool;
039 import org.apache.commons.math.random.RandomData;
040 import org.apache.commons.math.random.RandomDataImpl;
041 import org.apache.commons.performance.ConfigurationException;
042 import org.apache.commons.performance.ClientThread;
043 import org.apache.commons.performance.LoadGenerator;
044 import org.apache.commons.performance.Statistics;
045
046 /**
047 * Configurable load / performance tester for commons dbcp.
048 * Uses Commons Digester to parse and load configuration and spawns
049 * DBCPClientThread instances to generate load and gather statistics.
050 *
051 */
052 public class DBCPSoak extends LoadGenerator {
053 // Connection properties
054 private String driverClass;
055 private String connectUrl;
056 private String connectUser;
057 private String connectPassword;
058 private String queryType;
059
060 // Connection pool properties
061 private String poolType;
062 private String driverType;
063 private String factoryType;
064 private boolean autocommit;
065 private boolean readOnly;
066 private byte exhaustedAction;
067 private boolean testOnBorrow;
068 private boolean testOnReturn;
069 private long timeBetweenEvictions;
070 private int testsPerEviction;
071 private long idleTimeout;
072 private boolean testWhileIdle;
073 private String validationQuery;
074 private AbandonedConfig abandonedConfig = new AbandonedConfig();
075 private boolean poolPreparedStatements;
076 private int maxOpenStatements;
077 private int maxActive;
078 private int maxIdle;
079 private int minIdle;
080 private long maxWait;
081
082 // Instance variables
083 private GenericObjectPool connectionPool;
084 private PoolingDataSource dataSource;
085
086 /**
087 * Create connection pool and, if necessary, test table.
088 */
089 protected void init() throws Exception {
090 Class.forName(driverClass);
091
092 // Create object pool
093 if (poolType.equals("GenericObjectPool")) {
094 connectionPool = new GenericObjectPool(
095 null, maxActive, exhaustedAction,
096 maxWait, maxIdle, minIdle, testOnBorrow, testOnReturn,
097 timeBetweenEvictions, testsPerEviction, idleTimeout,
098 testWhileIdle);
099 } 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 }