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    }