/* * Copyright (C) 2011 The Android Open Source Project * * Licensed 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. */ import java.lang.reflect.*; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; // Run on host with: // javac ThreadTest.java && java ThreadStress && rm *.class // Through run-test: // test/run-test {run-test-args} 004-ThreadStress [Main {ThreadStress-args}] // (It is important to pass Main if you want to give parameters...) // // ThreadStress command line parameters: // -n X ............ number of threads // -d X ............ number of daemon threads // -o X ............ number of overall operations // -t X ............ number of operations per thread // --dumpmap ....... print the frequency map // -oom:X .......... frequency of OOM (double) // -alloc:X ........ frequency of Alloc // -stacktrace:X ... frequency of StackTrace // -exit:X ......... frequency of Exit // -sleep:X ........ frequency of Sleep // -wait:X ......... frequency of Wait // -timedwait:X .... frequency of TimedWait public class Main implements Runnable { public static final boolean DEBUG = false; private static abstract class Operation { /** * Perform the action represented by this operation. Returns true if the thread should * continue. */ public abstract boolean perform(); } private final static class OOM extends Operation { private final static int ALLOC_SIZE = 1024; @Override public boolean perform() { try { List<byte[]> l = new ArrayList<byte[]>(); while (true) { l.add(new byte[ALLOC_SIZE]); } } catch (OutOfMemoryError e) { } return true; } } private final static class SigQuit extends Operation { private final static int sigquit; private final static Method kill; private final static int pid; static { int pidTemp = -1; int sigquitTemp = -1; Method killTemp = null; try { Class<?> osClass = Class.forName("android.system.Os"); Method getpid = osClass.getDeclaredMethod("getpid"); pidTemp = (Integer)getpid.invoke(null); Class<?> osConstants = Class.forName("android.system.OsConstants"); Field sigquitField = osConstants.getDeclaredField("SIGQUIT"); sigquitTemp = (Integer)sigquitField.get(null); killTemp = osClass.getDeclaredMethod("kill", int.class, int.class); } catch (Exception e) { if (!e.getClass().getName().equals("ErrnoException")) { e.printStackTrace(System.out); } } pid = pidTemp; sigquit = sigquitTemp; kill = killTemp; } @Override public boolean perform() { try { kill.invoke(null, pid, sigquit); } catch (Exception e) { if (!e.getClass().getName().equals("ErrnoException")) { e.printStackTrace(System.out); } } return true; } } private final static class Alloc extends Operation { private final static int ALLOC_SIZE = 1024; // Needs to be small enough to not be in LOS. private final static int ALLOC_COUNT = 1024; @Override public boolean perform() { try { List<byte[]> l = new ArrayList<byte[]>(); for (int i = 0; i < ALLOC_COUNT; i++) { l.add(new byte[ALLOC_SIZE]); } } catch (OutOfMemoryError e) { } return true; } } private final static class LargeAlloc extends Operation { private final static int PAGE_SIZE = 4096; private final static int PAGE_SIZE_MODIFIER = 10; // Needs to be large enough for LOS. private final static int ALLOC_COUNT = 100; @Override public boolean perform() { try { List<byte[]> l = new ArrayList<byte[]>(); for (int i = 0; i < ALLOC_COUNT; i++) { l.add(new byte[PAGE_SIZE_MODIFIER * PAGE_SIZE]); } } catch (OutOfMemoryError e) { } return true; } } private final static class StackTrace extends Operation { @Override public boolean perform() { Thread.currentThread().getStackTrace(); return true; } } private final static class Exit extends Operation { @Override public boolean perform() { return false; } } private final static class Sleep extends Operation { private final static int SLEEP_TIME = 100; @Override public boolean perform() { try { Thread.sleep(SLEEP_TIME); } catch (InterruptedException ignored) { } return true; } } private final static class TimedWait extends Operation { private final static int SLEEP_TIME = 100; private final Object lock; public TimedWait(Object lock) { this.lock = lock; } @Override public boolean perform() { synchronized (lock) { try { lock.wait(SLEEP_TIME, 0); } catch (InterruptedException ignored) { } } return true; } } private final static class Wait extends Operation { private final Object lock; public Wait(Object lock) { this.lock = lock; } @Override public boolean perform() { synchronized (lock) { try { lock.wait(); } catch (InterruptedException ignored) { } } return true; } } private final static class SyncAndWork extends Operation { private final Object lock; public SyncAndWork(Object lock) { this.lock = lock; } @Override public boolean perform() { synchronized (lock) { try { Thread.sleep((int)(Math.random()*10)); } catch (InterruptedException ignored) { } } return true; } } private final static Map<Operation, Double> createDefaultFrequencyMap(Object lock) { Map<Operation, Double> frequencyMap = new HashMap<Operation, Double>(); frequencyMap.put(new OOM(), 0.005); // 1/200 frequencyMap.put(new SigQuit(), 0.095); // 19/200 frequencyMap.put(new Alloc(), 0.25); // 50/200 frequencyMap.put(new LargeAlloc(), 0.05); // 10/200 frequencyMap.put(new StackTrace(), 0.1); // 20/200 frequencyMap.put(new Exit(), 0.25); // 50/200 frequencyMap.put(new Sleep(), 0.125); // 25/200 frequencyMap.put(new TimedWait(lock), 0.05); // 10/200 frequencyMap.put(new Wait(lock), 0.075); // 15/200 return frequencyMap; } private final static Map<Operation, Double> createLockFrequencyMap(Object lock) { Map<Operation, Double> frequencyMap = new HashMap<Operation, Double>(); frequencyMap.put(new Sleep(), 0.2); frequencyMap.put(new TimedWait(lock), 0.2); frequencyMap.put(new Wait(lock), 0.2); frequencyMap.put(new SyncAndWork(lock), 0.4); return frequencyMap; } public static void main(String[] args) throws Exception { parseAndRun(args); } private static Map<Operation, Double> updateFrequencyMap(Map<Operation, Double> in, Object lock, String arg) { String split[] = arg.split(":"); if (split.length != 2) { throw new IllegalArgumentException("Can't split argument " + arg); } double d; try { d = Double.parseDouble(split[1]); } catch (Exception e) { throw new IllegalArgumentException(e); } if (d < 0) { throw new IllegalArgumentException(arg + ": value must be >= 0."); } Operation op = null; if (split[0].equals("-oom")) { op = new OOM(); } else if (split[0].equals("-sigquit")) { op = new SigQuit(); } else if (split[0].equals("-alloc")) { op = new Alloc(); } else if (split[0].equals("-largealloc")) { op = new LargeAlloc(); } else if (split[0].equals("-stacktrace")) { op = new StackTrace(); } else if (split[0].equals("-exit")) { op = new Exit(); } else if (split[0].equals("-sleep")) { op = new Sleep(); } else if (split[0].equals("-wait")) { op = new Wait(lock); } else if (split[0].equals("-timedwait")) { op = new TimedWait(lock); } else { throw new IllegalArgumentException("Unknown arg " + arg); } if (in == null) { in = new HashMap<Operation, Double>(); } in.put(op, d); return in; } private static void normalize(Map<Operation, Double> map) { double sum = 0; for (Double d : map.values()) { sum += d; } if (sum == 0) { throw new RuntimeException("No elements!"); } if (sum != 1.0) { // Avoid ConcurrentModificationException. Set<Operation> tmp = new HashSet<>(map.keySet()); for (Operation op : tmp) { map.put(op, map.get(op) / sum); } } } public static void parseAndRun(String[] args) throws Exception { int numberOfThreads = -1; int numberOfDaemons = -1; int totalOperations = -1; int operationsPerThread = -1; Object lock = new Object(); Map<Operation, Double> frequencyMap = null; boolean dumpMap = false; if (args != null) { // args[0] is libarttest for (int i = 1; i < args.length; i++) { if (args[i].equals("-n")) { i++; numberOfThreads = Integer.parseInt(args[i]); } else if (args[i].equals("-d")) { i++; numberOfDaemons = Integer.parseInt(args[i]); } else if (args[i].equals("-o")) { i++; totalOperations = Integer.parseInt(args[i]); } else if (args[i].equals("-t")) { i++; operationsPerThread = Integer.parseInt(args[i]); } else if (args[i].equals("--locks-only")) { lock = new Object(); frequencyMap = createLockFrequencyMap(lock); } else if (args[i].equals("--dumpmap")) { dumpMap = true; } else { frequencyMap = updateFrequencyMap(frequencyMap, lock, args[i]); } } } if (totalOperations != -1 && operationsPerThread != -1) { throw new IllegalArgumentException( "Specified both totalOperations and operationsPerThread"); } if (numberOfThreads == -1) { numberOfThreads = 5; } if (numberOfDaemons == -1) { numberOfDaemons = 3; } if (totalOperations == -1) { totalOperations = 1000; } if (operationsPerThread == -1) { operationsPerThread = totalOperations/numberOfThreads; } if (frequencyMap == null) { frequencyMap = createDefaultFrequencyMap(lock); } normalize(frequencyMap); if (dumpMap) { System.out.println(frequencyMap); } runTest(numberOfThreads, numberOfDaemons, operationsPerThread, lock, frequencyMap); } public static void runTest(final int numberOfThreads, final int numberOfDaemons, final int operationsPerThread, final Object lock, Map<Operation, Double> frequencyMap) throws Exception { // Each normal thread is going to do operationsPerThread // operations. Each daemon thread will loop over all // the operations and will not stop. // The distribution of operations is determined by // the Operation.frequency values. We fill out an Operation[] // for each thread with the operations it is to perform. The // Operation[] is shuffled so that there is more random // interactions between the threads. // Fill in the Operation[] array for each thread by laying // down references to operation according to their desired // frequency. // The first numberOfThreads elements are normal threads, the last // numberOfDaemons elements are daemon threads. final Main[] threadStresses = new Main[numberOfThreads + numberOfDaemons]; for (int t = 0; t < threadStresses.length; t++) { Operation[] operations = new Operation[operationsPerThread]; int o = 0; LOOP: while (true) { for (Operation op : frequencyMap.keySet()) { int freq = (int)(frequencyMap.get(op) * operationsPerThread); for (int f = 0; f < freq; f++) { if (o == operations.length) { break LOOP; } operations[o] = op; o++; } } } // Randomize the operation order Collections.shuffle(Arrays.asList(operations)); threadStresses[t] = t < numberOfThreads ? new Main(lock, t, operations) : new Daemon(lock, t, operations); } // Enable to dump operation counts per thread to make sure its // sane compared to Operation.frequency if (DEBUG) { for (int t = 0; t < threadStresses.length; t++) { Operation[] operations = threadStresses[t].operations; Map<Operation, Integer> distribution = new HashMap<Operation, Integer>(); for (Operation operation : operations) { Integer ops = distribution.get(operation); if (ops == null) { ops = 1; } else { ops++; } distribution.put(operation, ops); } System.out.println("Distribution for " + t); for (Operation op : frequencyMap.keySet()) { System.out.println(op + " = " + distribution.get(op)); } } } // Create the runners for each thread. The runner Thread // ensures that thread that exit due to Operation.EXIT will be // restarted until they reach their desired // operationsPerThread. Thread[] runners = new Thread[numberOfThreads]; for (int r = 0; r < runners.length; r++) { final Main ts = threadStresses[r]; runners[r] = new Thread("Runner thread " + r) { final Main threadStress = ts; public void run() { int id = threadStress.id; System.out.println("Starting worker for " + id); while (threadStress.nextOperation < operationsPerThread) { try { Thread thread = new Thread(ts, "Worker thread " + id); thread.start(); try { thread.join(); } catch (InterruptedException e) { } System.out.println("Thread exited for " + id + " with " + (operationsPerThread - threadStress.nextOperation) + " operations remaining."); } catch (OutOfMemoryError e) { // Ignore OOME since we need to print "Finishing worker" for the test // to pass. } } // Keep trying to print "Finishing worker" until it succeeds. while (true) { try { System.out.println("Finishing worker"); break; } catch (OutOfMemoryError e) { } } } }; } // The notifier thread is a daemon just loops forever to wake // up threads in Operation.WAIT if (lock != null) { Thread notifier = new Thread("Notifier") { public void run() { while (true) { synchronized (lock) { lock.notifyAll(); } } } }; notifier.setDaemon(true); notifier.start(); } // Create and start the daemon threads. for (int r = 0; r < numberOfDaemons; r++) { Main daemon = threadStresses[numberOfThreads + r]; Thread t = new Thread(daemon, "Daemon thread " + daemon.id); t.setDaemon(true); t.start(); } for (int r = 0; r < runners.length; r++) { runners[r].start(); } for (int r = 0; r < runners.length; r++) { runners[r].join(); } } protected final Operation[] operations; private final Object lock; protected final int id; private int nextOperation; private Main(Object lock, int id, Operation[] operations) { this.lock = lock; this.id = id; this.operations = operations; } public void run() { try { if (DEBUG) { System.out.println("Starting ThreadStress " + id); } while (nextOperation < operations.length) { Operation operation = operations[nextOperation]; if (DEBUG) { System.out.println("ThreadStress " + id + " operation " + nextOperation + " is " + operation); } nextOperation++; if (!operation.perform()) { return; } } } finally { if (DEBUG) { System.out.println("Finishing ThreadStress for " + id); } } } private static class Daemon extends Main { private Daemon(Object lock, int id, Operation[] operations) { super(lock, id, operations); } public void run() { try { if (DEBUG) { System.out.println("Starting ThreadStress Daemon " + id); } int i = 0; while (true) { Operation operation = operations[i]; if (DEBUG) { System.out.println("ThreadStress Daemon " + id + " operation " + i + " is " + operation); } operation.perform(); i = (i + 1) % operations.length; } } catch (OutOfMemoryError e) { // Catch OutOfMemoryErrors since these can cause the test to fail it they print // the stack trace after "Finishing worker". } finally { if (DEBUG) { System.out.println("Finishing ThreadStress Daemon for " + id); } } } } }