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 javax.sql.DataSource; 028 import org.apache.commons.dbcp.AbandonedConfig; 029 import org.apache.commons.dbcp.AbandonedObjectPool; 030 import org.apache.commons.dbcp.BasicDataSource; 031 import org.apache.commons.dbcp.ConnectionFactory; 032 import org.apache.commons.dbcp.DriverConnectionFactory; 033 import org.apache.commons.dbcp.DriverManagerConnectionFactory; 034 import org.apache.commons.dbcp.PoolableConnectionFactory; 035 import org.apache.commons.dbcp.PoolingDataSource; 036 import org.apache.commons.pool.KeyedObjectPoolFactory; 037 import org.apache.commons.pool.PoolableObjectFactory; 038 import org.apache.commons.pool.impl.GenericKeyedObjectPool; 039 import org.apache.commons.pool.impl.GenericKeyedObjectPoolFactory; 040 import org.apache.commons.pool.impl.GenericObjectPool; 041 import org.apache.commons.math.random.RandomData; 042 import org.apache.commons.math.random.RandomDataImpl; 043 import org.apache.commons.performance.ConfigurationException; 044 import org.apache.commons.performance.ClientThread; 045 import org.apache.commons.performance.LoadGenerator; 046 import org.apache.commons.performance.Statistics; 047 048 /** 049 * Configurable load / performance tester for commons dbcp. 050 * Uses Commons Digester to parse and load configuration and spawns 051 * DBCPClientThread instances to generate load and gather statistics. 052 * 053 */ 054 public class DBCPSoak extends LoadGenerator { 055 // Connection properties 056 private String driverClass; 057 private String connectUrl; 058 private String connectUser; 059 private String connectPassword; 060 private String queryType; 061 062 // Connection pool properties 063 private String poolType; 064 private String driverType; 065 private String factoryType; 066 private boolean autocommit; 067 private boolean readOnly; 068 private byte exhaustedAction; 069 private boolean testOnBorrow; 070 private boolean testOnReturn; 071 private long timeBetweenEvictions; 072 private int testsPerEviction; 073 private long idleTimeout; 074 private boolean testWhileIdle; 075 private String validationQuery; 076 private AbandonedConfig abandonedConfig = new AbandonedConfig(); 077 private boolean poolPreparedStatements; 078 private int maxOpenStatements; 079 private int maxActive; 080 private int maxIdle; 081 private int minIdle; 082 private long maxWait; 083 084 // DataSource type 085 private String dataSourceType; 086 087 // Instance variables 088 private GenericObjectPool connectionPool; 089 private DataSource dataSource; 090 091 /** 092 * Create connection pool and, if necessary, test table. 093 */ 094 protected void init() throws Exception { 095 096 if (dataSourceType.equals("BasicDataSource")) { 097 BasicDataSource bds = new BasicDataSource(); 098 bds.setDefaultAutoCommit(autocommit); 099 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 }