| /* |
| |
| Derby - Class |
| org.apache.derbyTesting.functionTests.tests.multi.StressMultiTest |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to you under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| |
| package org.apache.derbyTesting.functionTests.tests.multi; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.PrintStream; |
| import java.sql.Connection; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.sql.Statement; |
| import java.util.Date; |
| import java.util.Properties; |
| import java.util.Random; |
| import junit.framework.AssertionFailedError; |
| import junit.framework.Test; |
| import org.apache.derbyTesting.junit.BaseJDBCTestCase; |
| import org.apache.derbyTesting.junit.BaseTestCase; |
| import org.apache.derbyTesting.junit.BaseTestSuite; |
| import org.apache.derbyTesting.junit.CleanDatabaseTestSetup; |
| import org.apache.derbyTesting.junit.DatabasePropertyTestSetup; |
| import org.apache.derbyTesting.junit.Decorator; |
| import org.apache.derbyTesting.junit.JDBC; |
| import org.apache.derbyTesting.junit.SystemPropertyTestSetup; |
| import org.apache.derbyTesting.junit.TestConfiguration; |
| |
| /** |
| * Stress-test running any number of threads performing "random" operations on |
| * a database for any number of minutes. Default values are 10 threads for 10 |
| * minutes. The operations are create, select, insert, update and rollback. |
| * |
| * To test with a different number of threads and minutes set up a suite by |
| * calling getSuite(int threads, int minutes). See StressMulti10x1.java for |
| * an example. |
| * |
| * To run only the embedded run, use embeddedSuite(int threads, int minutes). |
| * |
| * The test will fail on the first exception thrown by any of the treads and |
| * this will be the reported cause of failure and the threads will be stopped. |
| * Other threads may throw exceptions before they have time to stop and these |
| * can be found in the log, but they will not be reported as errors or failures |
| * by the test. |
| * |
| * SQLExceptions are reported as failures and any other exceptions as errors. |
| * |
| * Some SQLExceptions are ignored by the test, but will show up in the log. |
| * |
| */ |
| public class StressMultiTest extends BaseJDBCTestCase { |
| |
| /** |
| * The number of threads the test will run. Default is 10 |
| */ |
| private static int THREADS = 10; |
| |
| /** |
| * The number of minutes the test will run. Default is 10. |
| */ |
| private static int MINUTES = 10; |
| |
| private static final String THREADSMINUTES = "derby.tests.ThreadsMinutes"; |
| |
| /** |
| * Force verbosity, used for debugging. Will print alot of information |
| * to the screen. |
| */ |
| private static boolean DEBUG = false; |
| |
| /** |
| * This holds the first throwable thrown by by any of the threads, |
| * and will thrown as the cause of failure for the fixture. |
| */ |
| private Throwable thrown = null; |
| |
| private Thread threads[] = null; |
| private Random rnd = new Random(); |
| |
| /** |
| * Setting this will cause the threads to terminate normally. |
| */ |
| private boolean complete = false; |
| |
| |
| public StressMultiTest(String s) { |
| super(s); |
| } |
| |
| /** |
| * Set up the testsuite to run in embedded, client and encryption mode. |
| * Default run is 10 threads for 10 minutes in each mode |
| */ |
| public static Test suite() { |
| Properties dbprops = new Properties(); |
| dbprops.put("derby.locks.deadlockTimeout", "2"); |
| dbprops.put("derby.locks.waitTimeout", "3"); |
| |
| Properties sysprops = new Properties(); |
| sysprops.put("derby.storage.keepTransactionLog", "true"); |
| sysprops.put("derby.language.logStatementText", "true"); |
| sysprops.put("derby.infolog.append", "true"); |
| Test embedded = new BaseTestSuite(StressMultiTest.class); |
| embedded = new SystemPropertyTestSetup(embedded,sysprops,true); |
| embedded = new DatabasePropertyTestSetup(embedded,dbprops); |
| // make this a singleUseDatabase so the datbase and |
| // transaction log will be preserved. |
| embedded = TestConfiguration.singleUseDatabaseDecorator(newCleanDatabase(embedded)); |
| // SystemPropertyTestSetup for static properties |
| // does not work for client because shutting down the |
| // engine causes protocol errors on the client. Run |
| // with -Dderby.storage.keepTransactionLog=true if |
| // you need to save the transaction log for client. |
| Test client = TestConfiguration.clientServerDecorator( |
| new BaseTestSuite(StressMultiTest.class)); |
| client = newCleanDatabase(new DatabasePropertyTestSetup(client,dbprops)); |
| Test encrypted = new BaseTestSuite(StressMultiTest.class); |
| // SystemPropertyTestSetup for static properties |
| // does not work for encrypted databases because the |
| // database has to be rebooted and we don't have access |
| // to the boot password (local to Decorator.encryptedDatabase() |
| // Run with -Dderby.storage.keepTransactionLog=true if you |
| // need to save the transaction log for encrypted. |
| BaseTestSuite unencrypted = |
| new BaseTestSuite("StressMultiTest:unencrypted"); |
| |
| unencrypted.addTest((embedded)); |
| unencrypted.addTest((client)); |
| |
| BaseTestSuite suite = |
| new BaseTestSuite("StressMultiTest, " + THREADS + |
| " Threads " + MINUTES + " Minutes"); |
| |
| suite.addTest(newCleanDatabase(unencrypted)); |
| //Encrypted uses a different database so it needs its own newCleanDatabase |
| suite.addTest(Decorator.encryptedDatabase(new DatabasePropertyTestSetup(newCleanDatabase(encrypted),dbprops))); |
| |
| return suite; |
| } |
| |
| /** |
| * Get a testsuite that runs all the 3 runs (embedded, client and encrypted) |
| * with the given number of threads for the given number of minutes. |
| * |
| * @param threads |
| * @param minutes |
| * @return suite after changing <code>THREADS</code> and |
| * <code> MINUTES </code> |
| */ |
| public static Test suite(int threads, int minutes) { |
| THREADS = threads; |
| MINUTES = minutes; |
| return suite(); |
| } |
| |
| /** |
| * Get at testsuite that runs only the embedded suite with |
| * the given number of threads for the given number of minutes. |
| * |
| * @param threads |
| * @param minutes |
| */ |
| public static Test embeddedSuite(int threads, int minutes) { |
| THREADS = threads; |
| MINUTES = minutes; |
| |
| Properties dbprops = new Properties(); |
| dbprops.put("derby.locks.deadlockTimeout", "2"); |
| dbprops.put("derby.locks.waitTimeout", "3"); |
| dbprops.put("derby.language.logStatementText", "true"); |
| dbprops.put("derby.storage.keepTransactionLog", "true"); |
| Properties sysprops = new Properties(); |
| sysprops.put("derby.storage.keepTransactionLog", "true"); |
| sysprops.put("derby.language.logStatementText", "true"); |
| sysprops.put("derby.infolog.append", "true"); |
| Test embedded = new BaseTestSuite(StressMultiTest.class); |
| embedded = new SystemPropertyTestSetup(embedded,sysprops,true); |
| embedded = new DatabasePropertyTestSetup(embedded,dbprops); |
| embedded = TestConfiguration.singleUseDatabaseDecorator(newCleanDatabase(embedded)); |
| return embedded; |
| } |
| |
| /* |
| * Create a CleanDatabaseTestSetup that sets up the testdatabase. |
| */ |
| private static Test newCleanDatabase(Test s) { |
| return new CleanDatabaseTestSetup(s) { |
| /** |
| * Creates the database objects used in the test cases. |
| * |
| * @throws SQLException |
| */ |
| protected void decorateSQL(Statement s) throws SQLException { |
| s.execute("CREATE FUNCTION PADSTRING (DATA VARCHAR(32000), " |
| + "LENGTH INTEGER) RETURNS VARCHAR(32000) EXTERNAL NAME " + |
| "'org.apache.derbyTesting.functionTests.util.Formatters" + |
| ".padString' LANGUAGE JAVA PARAMETER STYLE JAVA"); |
| s.execute("CREATE FUNCTION RANDOM() RETURNS DOUBLE EXTERNAL " + |
| "NAME 'java.lang.Math.random' LANGUAGE JAVA PARAMETER " + |
| "STYLE JAVA"); |
| s.execute("create table main(x int not null primary key," + |
| " y varchar(2000))"); |
| s.execute("insert into main values(1, PADSTRING('aaaa',2000))"); |
| s.execute("insert into main values(2, PADSTRING('aaaa',2000))"); |
| s.execute("insert into main values(3, PADSTRING('aaaa',2000))"); |
| s.execute("insert into main values(4, PADSTRING('aaaa',2000))"); |
| s.execute("insert into main values(5, PADSTRING('aaaa',2000))"); |
| s.execute("insert into main values(6, PADSTRING('aaaa',2000))"); |
| s.execute("insert into main values(7, PADSTRING('aaaa',2000))"); |
| s.execute("insert into main values(8, PADSTRING('aaaa',2000))"); |
| s.execute("insert into main values(9, PADSTRING('aaaa',2000))"); |
| s.execute("insert into main values(10, PADSTRING('aaaa',2000))"); |
| s.execute("insert into main values(12, PADSTRING('aaaa',2000))"); |
| s.execute("insert into main values(13, PADSTRING('aaaa',2000))"); |
| s.execute("create table main2(x int not null primary key," + |
| " y varchar(2000))"); |
| s.execute("insert into main2 values(1, PADSTRING('aaaa',2000))"); |
| s.execute("insert into main2 values(2, PADSTRING('aaaa',2000))"); |
| s.execute("insert into main2 values(3, PADSTRING('aaaa',2000))"); |
| s.execute("insert into main2 values(4, PADSTRING('aaaa',2000))"); |
| s.execute("insert into main2 values(5, PADSTRING('aaaa',2000))"); |
| s.execute("insert into main2 values(6, PADSTRING('aaaa',2000))"); |
| s.execute("insert into main2 values(7, PADSTRING('aaaa',2000))"); |
| s.execute("insert into main2 values(8, PADSTRING('aaaa',2000))"); |
| s.execute("insert into main2 values(9, PADSTRING('aaaa',2000))"); |
| s.execute("insert into main2 values(10, PADSTRING('aaaa',2000))"); |
| s.execute("insert into main2 values(12, PADSTRING('aaaa',2000))"); |
| s.execute("insert into main2 values(13, PADSTRING('aaaa',2000))"); |
| getConnection().commit(); |
| }; |
| }; |
| } |
| |
| |
| /* |
| * (non-Javadoc) |
| * @see junit.framework.TestCase#setUp() |
| */ |
| public void setUp() throws Exception{ |
| super.setUp(); |
| this.getTestConfiguration().setVerbosity(DEBUG); |
| |
| // Let -Dderby.tests.ThreadsMinutes=TTxMM override. |
| String optThreadsMinutes = getSystemProperty(THREADSMINUTES); |
| if ( optThreadsMinutes != null ) |
| { // Syntax: '99x22' meaning 99 threads 22 minutes. |
| int xPos = optThreadsMinutes.indexOf("x"); |
| try{ |
| // Assuming xPos >= 1 : substring or parseInt will catch it. |
| THREADS = Integer.parseInt(optThreadsMinutes.substring(0, xPos)); |
| MINUTES = Integer.parseInt(optThreadsMinutes.substring(xPos+1, optThreadsMinutes.length())); |
| } |
| catch ( Exception e) { |
| alarm("Illegal value for '"+THREADSMINUTES+"': '" |
| +optThreadsMinutes+"' - " +e.getMessage() |
| +". Threads: " + THREADS +", minutes: " + MINUTES); |
| } |
| traceit("Threads: " + THREADS +", minutes: " + MINUTES); |
| } |
| } |
| |
| /* |
| * Make sure we clear the fields when done. |
| */ |
| public void tearDown() throws Exception{ |
| rnd = null; |
| threads = null; |
| super.tearDown(); |
| } |
| |
| /** |
| * This is the actual fixture run by the JUnit framework. |
| * Creates all the runnables we need and pass them on to |
| * runTestCaseRunnables. If any exception was thrown |
| * when they are done it is thrown for JUnit to catch and |
| * handle normally. |
| */ |
| public void testStressMulti() throws Throwable{ |
| |
| thrown = null; |
| |
| StressMultiRunnable[] tct = new StressMultiRunnable[THREADS]; |
| |
| for (int i = 0; i < tct.length; i++) { |
| tct[i] = new StressMultiRunnable ("Tester" + i, MINUTES); |
| } |
| runTestCaseRunnables (tct); |
| tct = null; |
| |
| if (thrown!=null) throw thrown; |
| } |
| |
| |
| /* |
| * Create all the threads and run them until they finish. |
| * |
| * @param runnables |
| */ |
| protected void runTestCaseRunnables(final StressMultiRunnable[] runnables) { |
| if (runnables == null) { |
| throw new IllegalArgumentException("runnables is null"); |
| } |
| threads = new Thread[runnables.length]; |
| |
| //Create threads |
| for (int i = 0; i < threads.length; i++) { |
| threads[i] = new Thread(runnables[i]); |
| } |
| //Run the threads |
| for (int i = 0; i < threads.length; i++) { |
| threads[i].start(); |
| } |
| //Wait for the threads to finish |
| try { |
| for (int i = 0; i < threads.length; i++) { |
| threads[i].join(); |
| } |
| } catch (InterruptedException ignore) { |
| BaseTestCase.println("Thread join interrupted."); |
| } |
| threads = null; |
| } |
| |
| |
| /* |
| * Handles any exceptions we get in the threads |
| */ |
| private synchronized void handleException(final Throwable t, String message) { |
| complete = true; //This stops the threads. |
| if (thrown == null) { |
| if(t instanceof AssertionFailedError) { |
| thrown = (AssertionFailedError) t; |
| } else if (t instanceof SQLException) { |
| ByteArrayOutputStream b = new ByteArrayOutputStream(); |
| t.printStackTrace(new PrintStream(b)); |
| thrown = new AssertionFailedError("Caused by: \n" + b.toString()); |
| } |
| else { |
| thrown = t; |
| } |
| println("Exception handled!!: "+ message + " - " + t); |
| } else |
| println("Exception discarded because another was already caught and the threads are terminating..:\n"+ message + " - " + t); |
| } |
| |
| |
| /** |
| * This subclass contains the actual stresstests that will be run in |
| * multiple threads. |
| */ |
| class StressMultiRunnable implements Runnable { |
| String name; |
| Connection con; |
| long starttime; |
| long runtime; //How long will this thread run |
| |
| public StressMultiRunnable(String name, int minutes) { |
| super(); |
| this.name = name; |
| starttime = System.currentTimeMillis(); |
| runtime = minutes*60*1000; //convert minutes to ms |
| try { |
| con = openDefaultConnection(); |
| con.setAutoCommit(false); |
| } catch (SQLException e) { |
| println(e.toString()); |
| } |
| } |
| |
| /* |
| * Do the work. Runs an infinite loop picking random operations from |
| * the methods below until either complete == true, ie. there was a |
| * failure in one of the threads, or the specified time has passed. |
| */ |
| public void run() { |
| try { |
| int i = 0; |
| while (!complete) { |
| i++; |
| int r = rnd.nextInt(100); |
| if (r < 10) { |
| String n = "x"; |
| switch (rnd.nextInt(4)) { |
| case 0: n = "a"; break; |
| case 1: n = "x"; break; |
| case 2: n = "y"; break; |
| case 3: n = "z"; break; |
| } |
| create(n); |
| println(name + " - Run " + i + " - Create " + n + " " + |
| new Date(System.currentTimeMillis()).toString()); |
| } else if (r < 25){ |
| String n = "main"; |
| if (rnd.nextInt(2) == 1) n = "main2"; |
| roll(n); |
| println(name + " - Run " + i + " - Roll " + n + " " + |
| new Date(System.currentTimeMillis()).toString()); |
| } else if (r < 40){ |
| String n = "main"; |
| if (rnd.nextInt(2) == 1) n = "main2"; |
| insert(n); |
| println(name + " - Run " + i + " - Insert " + n + " " + |
| new Date(System.currentTimeMillis()).toString()); |
| } else if (r < 60){ |
| String n = "main"; |
| if (rnd.nextInt(2) == 1) n = "main2"; |
| update(n); |
| println(name + " - Run " + i + " - Update " + |
| n + " " + |
| new Date(System.currentTimeMillis()).toString()); |
| } else if (r <= 99){ |
| String n = "main"; |
| if (rnd.nextInt(2) == 1) n = "main2"; |
| select(n); |
| println(name + " - Run " + i + " - Select " + n + " " + |
| new Date(System.currentTimeMillis()).toString()); |
| } |
| //Break the loop if the running time is reached. |
| if ((starttime + runtime) <= System.currentTimeMillis()) { |
| println(name + " - STOPPING - " + |
| new Date(System.currentTimeMillis()).toString()); |
| break; |
| } |
| Thread.sleep(rnd.nextInt(10)); //Just to spread them out a bit. |
| } |
| } |
| catch(Throwable t) { |
| println("Exception in " + name + ": " + t); |
| |
| handleException(t, name + " - " + |
| new Date(System.currentTimeMillis()).toString()); |
| } |
| |
| println(name + " terminated!"); |
| } |
| |
| |
| /********* Below are the tasks done by the threads ******************/ |
| |
| /** |
| * Create a table with the given name and then drop it |
| * |
| * @param table |
| * @throws SQLException |
| */ |
| private void create(String table) throws SQLException { |
| Statement s = con.createStatement(); |
| try { |
| s.execute("create table " + table + " (x int)"); |
| s.execute("insert into " + table + " values (1)"); |
| s.execute("insert into " + table + " values (1)"); |
| s.execute("insert into " + table + " values (1)"); |
| s.execute("insert into " + table + " values (1)"); |
| s.execute("insert into " + table + " values (1)"); |
| s.execute("drop table " + table); |
| con.commit(); |
| } catch (SQLException se) { |
| String e = se.getSQLState(); |
| if (e.equals("X0X08") || e.equals("X0X05") || e.equals("42X05") |
| || e.equals("42Y55") || e.equals("42000") |
| || e.equals("40001") || e.equals("40XL1") |
| || e.equals("40XL2") || e.equals("42Y07") |
| || e.equals("42Y55") |
| ) { |
| //Ignore these |
| } else { |
| throw se; |
| } |
| } finally { |
| s = null; |
| } |
| } |
| |
| /** |
| * Insert a random value into the given table. |
| * Table names can be main or main2. |
| * |
| * @param table |
| * @throws SQLException |
| */ |
| private void insert(String table) throws SQLException { |
| Statement s = con.createStatement(); |
| try { |
| s.executeUpdate("insert into " + table |
| + " values (random() * 1000 + 100, 'rand')"); |
| con.commit(); |
| } catch (SQLException se) { |
| String e = se.getSQLState(); |
| if (e.equals("42000") || e.equals("23505") || e.equals("40001") |
| || e.equals("40XL1") || e.equals("40XL2") |
| || e.equals("42Y07") || e.equals("42Y55")) { |
| // ignore these |
| } else { |
| throw se; |
| } |
| }finally { |
| s = null; |
| } |
| } |
| |
| /** |
| * insert a value into the given table, then rollback. |
| * Table names are main or main2. |
| * |
| * @param table |
| * @throws SQLException |
| */ |
| private void roll(String table) throws SQLException { |
| Statement s = con.createStatement(); |
| con.setAutoCommit(false); |
| try { |
| s.executeUpdate("insert into " + table |
| + " values (666, '666')"); |
| con.rollback(); |
| } catch (SQLException se) { |
| String e = se.getSQLState(); |
| if (e.equals("X0X05") || e.equals("42X05") || e.equals("42Y55") |
| || e.equals("42000") || e.equals("23505") |
| || e.equals("40001") || e.equals("40XL1") |
| || e.equals("40XL2") || e.equals("42Y07") |
| || e.equals("42Y55")) { |
| // ignore these |
| } else { |
| throw se; |
| } |
| }finally { |
| s = null; |
| } |
| } |
| |
| /** |
| * Select * from the given table. Table names are main or main2. |
| * |
| * @param table |
| * @throws SQLException |
| */ |
| private void select(String table) throws SQLException { |
| Statement s = con.createStatement(); |
| try { |
| ResultSet rs = s.executeQuery("select * from " + table); |
| JDBC.assertDrainResults(rs); |
| } catch (SQLException se) { |
| String e = se.getSQLState(); |
| if (e.equals("42Y55") || e.equals("42000") || e.equals("40001") |
| || e.equals("40XL1") || e.equals("40XL2") |
| || e.equals("42Y07")) { |
| // ignore these |
| } else { |
| throw se; |
| } |
| }finally { |
| s = null; |
| } |
| } |
| |
| /** |
| * Update the given table. Table names are main or main2. |
| * |
| * @param table |
| * @throws SQLException |
| */ |
| private void update(String table) throws SQLException { |
| Statement s = con.createStatement(); |
| try { |
| s.executeUpdate("update " + table |
| + " main set y = 'zzz' where x = 5"); |
| } catch (SQLException se) { |
| String e = se.getSQLState(); |
| if (e.equals("42Y55") || e.equals("42000") || e.equals("40001") |
| || e.equals("40XL1") || e.equals("40XL2") |
| || e.equals("42Y07")) { |
| // ignore these |
| } else { |
| throw se; |
| } |
| } finally { |
| s = null; |
| } |
| } |
| } |
| } |