Home | History | Annotate | Download | only in src
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 import java.lang.reflect.*;
     18 import java.util.ArrayList;
     19 import java.util.Arrays;
     20 import java.util.Collections;
     21 import java.util.HashMap;
     22 import java.util.HashSet;
     23 import java.util.List;
     24 import java.util.Map;
     25 import java.util.Set;
     26 
     27 // Run on host with:
     28 //   javac ThreadTest.java && java ThreadStress && rm *.class
     29 // Through run-test:
     30 //   test/run-test {run-test-args} 004-ThreadStress [Main {ThreadStress-args}]
     31 //   (It is important to pass Main if you want to give parameters...)
     32 //
     33 // ThreadStress command line parameters:
     34 //    -n X ............ number of threads
     35 //    -d X ............ number of daemon threads
     36 //    -o X ............ number of overall operations
     37 //    -t X ............ number of operations per thread
     38 //    --dumpmap ....... print the frequency map
     39 //    -oom:X .......... frequency of OOM (double)
     40 //    -alloc:X ........ frequency of Alloc
     41 //    -stacktrace:X ... frequency of StackTrace
     42 //    -exit:X ......... frequency of Exit
     43 //    -sleep:X ........ frequency of Sleep
     44 //    -wait:X ......... frequency of Wait
     45 //    -timedwait:X .... frequency of TimedWait
     46 
     47 public class Main implements Runnable {
     48 
     49     public static final boolean DEBUG = false;
     50 
     51     private static abstract class Operation {
     52         /**
     53          * Perform the action represented by this operation. Returns true if the thread should
     54          * continue.
     55          */
     56         public abstract boolean perform();
     57     }
     58 
     59     private final static class OOM extends Operation {
     60         private final static int ALLOC_SIZE = 1024;
     61 
     62         @Override
     63         public boolean perform() {
     64             try {
     65                 List<byte[]> l = new ArrayList<byte[]>();
     66                 while (true) {
     67                     l.add(new byte[ALLOC_SIZE]);
     68                 }
     69             } catch (OutOfMemoryError e) {
     70             }
     71             return true;
     72         }
     73     }
     74 
     75     private final static class SigQuit extends Operation {
     76         private final static int sigquit;
     77         private final static Method kill;
     78         private final static int pid;
     79 
     80         static {
     81             int pidTemp = -1;
     82             int sigquitTemp = -1;
     83             Method killTemp = null;
     84 
     85             try {
     86                 Class<?> osClass = Class.forName("android.system.Os");
     87                 Method getpid = osClass.getDeclaredMethod("getpid");
     88                 pidTemp = (Integer)getpid.invoke(null);
     89 
     90                 Class<?> osConstants = Class.forName("android.system.OsConstants");
     91                 Field sigquitField = osConstants.getDeclaredField("SIGQUIT");
     92                 sigquitTemp = (Integer)sigquitField.get(null);
     93 
     94                 killTemp = osClass.getDeclaredMethod("kill", int.class, int.class);
     95             } catch (Exception e) {
     96                 if (!e.getClass().getName().equals("ErrnoException")) {
     97                     e.printStackTrace(System.out);
     98                 }
     99             }
    100 
    101             pid = pidTemp;
    102             sigquit = sigquitTemp;
    103             kill = killTemp;
    104         }
    105 
    106         @Override
    107         public boolean perform() {
    108             try {
    109                 kill.invoke(null, pid, sigquit);
    110             } catch (Exception e) {
    111                 if (!e.getClass().getName().equals("ErrnoException")) {
    112                     e.printStackTrace(System.out);
    113                 }
    114             }
    115             return true;
    116         }
    117     }
    118 
    119     private final static class Alloc extends Operation {
    120         private final static int ALLOC_SIZE = 1024;  // Needs to be small enough to not be in LOS.
    121         private final static int ALLOC_COUNT = 1024;
    122 
    123         @Override
    124         public boolean perform() {
    125             try {
    126                 List<byte[]> l = new ArrayList<byte[]>();
    127                 for (int i = 0; i < ALLOC_COUNT; i++) {
    128                     l.add(new byte[ALLOC_SIZE]);
    129                 }
    130             } catch (OutOfMemoryError e) {
    131             }
    132             return true;
    133         }
    134     }
    135 
    136     private final static class LargeAlloc extends Operation {
    137         private final static int PAGE_SIZE = 4096;
    138         private final static int PAGE_SIZE_MODIFIER = 10;  // Needs to be large enough for LOS.
    139         private final static int ALLOC_COUNT = 100;
    140 
    141         @Override
    142         public boolean perform() {
    143             try {
    144                 List<byte[]> l = new ArrayList<byte[]>();
    145                 for (int i = 0; i < ALLOC_COUNT; i++) {
    146                     l.add(new byte[PAGE_SIZE_MODIFIER * PAGE_SIZE]);
    147                 }
    148             } catch (OutOfMemoryError e) {
    149             }
    150             return true;
    151         }
    152     }
    153 
    154     private final static class StackTrace extends Operation {
    155         @Override
    156         public boolean perform() {
    157             Thread.currentThread().getStackTrace();
    158             return true;
    159         }
    160     }
    161 
    162     private final static class Exit extends Operation {
    163         @Override
    164         public boolean perform() {
    165             return false;
    166         }
    167     }
    168 
    169     private final static class Sleep extends Operation {
    170         private final static int SLEEP_TIME = 100;
    171 
    172         @Override
    173         public boolean perform() {
    174             try {
    175                 Thread.sleep(SLEEP_TIME);
    176             } catch (InterruptedException ignored) {
    177             }
    178             return true;
    179         }
    180     }
    181 
    182     private final static class TimedWait extends Operation {
    183         private final static int SLEEP_TIME = 100;
    184 
    185         private final Object lock;
    186 
    187         public TimedWait(Object lock) {
    188             this.lock = lock;
    189         }
    190 
    191         @Override
    192         public boolean perform() {
    193             synchronized (lock) {
    194                 try {
    195                     lock.wait(SLEEP_TIME, 0);
    196                 } catch (InterruptedException ignored) {
    197                 }
    198             }
    199             return true;
    200         }
    201     }
    202 
    203     private final static class Wait extends Operation {
    204         private final Object lock;
    205 
    206         public Wait(Object lock) {
    207             this.lock = lock;
    208         }
    209 
    210         @Override
    211         public boolean perform() {
    212             synchronized (lock) {
    213                 try {
    214                     lock.wait();
    215                 } catch (InterruptedException ignored) {
    216                 }
    217             }
    218             return true;
    219         }
    220     }
    221 
    222     private final static class SyncAndWork extends Operation {
    223         private final Object lock;
    224 
    225         public SyncAndWork(Object lock) {
    226             this.lock = lock;
    227         }
    228 
    229         @Override
    230         public boolean perform() {
    231             synchronized (lock) {
    232                 try {
    233                     Thread.sleep((int)(Math.random()*10));
    234                 } catch (InterruptedException ignored) {
    235                 }
    236             }
    237             return true;
    238         }
    239     }
    240 
    241     private final static Map<Operation, Double> createDefaultFrequencyMap(Object lock) {
    242         Map<Operation, Double> frequencyMap = new HashMap<Operation, Double>();
    243         frequencyMap.put(new OOM(), 0.005);             //  1/200
    244         frequencyMap.put(new SigQuit(), 0.095);         // 19/200
    245         frequencyMap.put(new Alloc(), 0.25);            // 50/200
    246         frequencyMap.put(new LargeAlloc(), 0.05);       // 10/200
    247         frequencyMap.put(new StackTrace(), 0.1);        // 20/200
    248         frequencyMap.put(new Exit(), 0.25);             // 50/200
    249         frequencyMap.put(new Sleep(), 0.125);           // 25/200
    250         frequencyMap.put(new TimedWait(lock), 0.05);    // 10/200
    251         frequencyMap.put(new Wait(lock), 0.075);        // 15/200
    252 
    253         return frequencyMap;
    254     }
    255 
    256     private final static Map<Operation, Double> createLockFrequencyMap(Object lock) {
    257       Map<Operation, Double> frequencyMap = new HashMap<Operation, Double>();
    258       frequencyMap.put(new Sleep(), 0.2);
    259       frequencyMap.put(new TimedWait(lock), 0.2);
    260       frequencyMap.put(new Wait(lock), 0.2);
    261       frequencyMap.put(new SyncAndWork(lock), 0.4);
    262 
    263       return frequencyMap;
    264     }
    265 
    266     public static void main(String[] args) throws Exception {
    267         parseAndRun(args);
    268     }
    269 
    270     private static Map<Operation, Double> updateFrequencyMap(Map<Operation, Double> in,
    271             Object lock, String arg) {
    272         String split[] = arg.split(":");
    273         if (split.length != 2) {
    274             throw new IllegalArgumentException("Can't split argument " + arg);
    275         }
    276         double d;
    277         try {
    278             d = Double.parseDouble(split[1]);
    279         } catch (Exception e) {
    280             throw new IllegalArgumentException(e);
    281         }
    282         if (d < 0) {
    283             throw new IllegalArgumentException(arg + ": value must be >= 0.");
    284         }
    285         Operation op = null;
    286         if (split[0].equals("-oom")) {
    287             op = new OOM();
    288         } else if (split[0].equals("-sigquit")) {
    289             op = new SigQuit();
    290         } else if (split[0].equals("-alloc")) {
    291             op = new Alloc();
    292         } else if (split[0].equals("-largealloc")) {
    293             op = new LargeAlloc();
    294         } else if (split[0].equals("-stacktrace")) {
    295             op = new StackTrace();
    296         } else if (split[0].equals("-exit")) {
    297             op = new Exit();
    298         } else if (split[0].equals("-sleep")) {
    299             op = new Sleep();
    300         } else if (split[0].equals("-wait")) {
    301             op = new Wait(lock);
    302         } else if (split[0].equals("-timedwait")) {
    303             op = new TimedWait(lock);
    304         } else {
    305             throw new IllegalArgumentException("Unknown arg " + arg);
    306         }
    307 
    308         if (in == null) {
    309             in = new HashMap<Operation, Double>();
    310         }
    311         in.put(op, d);
    312 
    313         return in;
    314     }
    315 
    316     private static void normalize(Map<Operation, Double> map) {
    317         double sum = 0;
    318         for (Double d : map.values()) {
    319             sum += d;
    320         }
    321         if (sum == 0) {
    322             throw new RuntimeException("No elements!");
    323         }
    324         if (sum != 1.0) {
    325             // Avoid ConcurrentModificationException.
    326             Set<Operation> tmp = new HashSet<>(map.keySet());
    327             for (Operation op : tmp) {
    328                 map.put(op, map.get(op) / sum);
    329             }
    330         }
    331     }
    332 
    333     public static void parseAndRun(String[] args) throws Exception {
    334         int numberOfThreads = -1;
    335         int numberOfDaemons = -1;
    336         int totalOperations = -1;
    337         int operationsPerThread = -1;
    338         Object lock = new Object();
    339         Map<Operation, Double> frequencyMap = null;
    340         boolean dumpMap = false;
    341 
    342         if (args != null) {
    343             // args[0] is libarttest
    344             for (int i = 1; i < args.length; i++) {
    345                 if (args[i].equals("-n")) {
    346                     i++;
    347                     numberOfThreads = Integer.parseInt(args[i]);
    348                 } else if (args[i].equals("-d")) {
    349                     i++;
    350                     numberOfDaemons = Integer.parseInt(args[i]);
    351                 } else if (args[i].equals("-o")) {
    352                     i++;
    353                     totalOperations = Integer.parseInt(args[i]);
    354                 } else if (args[i].equals("-t")) {
    355                     i++;
    356                     operationsPerThread = Integer.parseInt(args[i]);
    357                 } else if (args[i].equals("--locks-only")) {
    358                     lock = new Object();
    359                     frequencyMap = createLockFrequencyMap(lock);
    360                 } else if (args[i].equals("--dumpmap")) {
    361                     dumpMap = true;
    362                 } else {
    363                     frequencyMap = updateFrequencyMap(frequencyMap, lock, args[i]);
    364                 }
    365             }
    366         }
    367 
    368         if (totalOperations != -1 && operationsPerThread != -1) {
    369             throw new IllegalArgumentException(
    370                     "Specified both totalOperations and operationsPerThread");
    371         }
    372 
    373         if (numberOfThreads == -1) {
    374             numberOfThreads = 5;
    375         }
    376 
    377         if (numberOfDaemons == -1) {
    378             numberOfDaemons = 3;
    379         }
    380 
    381         if (totalOperations == -1) {
    382             totalOperations = 1000;
    383         }
    384 
    385         if (operationsPerThread == -1) {
    386             operationsPerThread = totalOperations/numberOfThreads;
    387         }
    388 
    389         if (frequencyMap == null) {
    390             frequencyMap = createDefaultFrequencyMap(lock);
    391         }
    392         normalize(frequencyMap);
    393 
    394         if (dumpMap) {
    395             System.out.println(frequencyMap);
    396         }
    397 
    398         runTest(numberOfThreads, numberOfDaemons, operationsPerThread, lock, frequencyMap);
    399     }
    400 
    401     public static void runTest(final int numberOfThreads, final int numberOfDaemons,
    402                                final int operationsPerThread, final Object lock,
    403                                Map<Operation, Double> frequencyMap) throws Exception {
    404         // Each normal thread is going to do operationsPerThread
    405         // operations. Each daemon thread will loop over all
    406         // the operations and will not stop.
    407         // The distribution of operations is determined by
    408         // the Operation.frequency values. We fill out an Operation[]
    409         // for each thread with the operations it is to perform. The
    410         // Operation[] is shuffled so that there is more random
    411         // interactions between the threads.
    412 
    413         // Fill in the Operation[] array for each thread by laying
    414         // down references to operation according to their desired
    415         // frequency.
    416         // The first numberOfThreads elements are normal threads, the last
    417         // numberOfDaemons elements are daemon threads.
    418         final Main[] threadStresses = new Main[numberOfThreads + numberOfDaemons];
    419         for (int t = 0; t < threadStresses.length; t++) {
    420             Operation[] operations = new Operation[operationsPerThread];
    421             int o = 0;
    422             LOOP:
    423             while (true) {
    424                 for (Operation op : frequencyMap.keySet()) {
    425                     int freq = (int)(frequencyMap.get(op) * operationsPerThread);
    426                     for (int f = 0; f < freq; f++) {
    427                         if (o == operations.length) {
    428                             break LOOP;
    429                         }
    430                         operations[o] = op;
    431                         o++;
    432                     }
    433                 }
    434             }
    435             // Randomize the operation order
    436             Collections.shuffle(Arrays.asList(operations));
    437             threadStresses[t] = t < numberOfThreads ? new Main(lock, t, operations) :
    438                                                       new Daemon(lock, t, operations);
    439         }
    440 
    441         // Enable to dump operation counts per thread to make sure its
    442         // sane compared to Operation.frequency
    443         if (DEBUG) {
    444             for (int t = 0; t < threadStresses.length; t++) {
    445                 Operation[] operations = threadStresses[t].operations;
    446                 Map<Operation, Integer> distribution = new HashMap<Operation, Integer>();
    447                 for (Operation operation : operations) {
    448                     Integer ops = distribution.get(operation);
    449                     if (ops == null) {
    450                         ops = 1;
    451                     } else {
    452                         ops++;
    453                     }
    454                     distribution.put(operation, ops);
    455                 }
    456                 System.out.println("Distribution for " + t);
    457                 for (Operation op : frequencyMap.keySet()) {
    458                     System.out.println(op + " = " + distribution.get(op));
    459                 }
    460             }
    461         }
    462 
    463         // Create the runners for each thread. The runner Thread
    464         // ensures that thread that exit due to Operation.EXIT will be
    465         // restarted until they reach their desired
    466         // operationsPerThread.
    467         Thread[] runners = new Thread[numberOfThreads];
    468         for (int r = 0; r < runners.length; r++) {
    469             final Main ts = threadStresses[r];
    470             runners[r] = new Thread("Runner thread " + r) {
    471                 final Main threadStress = ts;
    472                 public void run() {
    473                     int id = threadStress.id;
    474                     System.out.println("Starting worker for " + id);
    475                     while (threadStress.nextOperation < operationsPerThread) {
    476                         try {
    477                             Thread thread = new Thread(ts, "Worker thread " + id);
    478                             thread.start();
    479                             try {
    480                                 thread.join();
    481                             } catch (InterruptedException e) {
    482                             }
    483 
    484                             System.out.println("Thread exited for " + id + " with "
    485                                                + (operationsPerThread - threadStress.nextOperation)
    486                                                + " operations remaining.");
    487                         } catch (OutOfMemoryError e) {
    488                             // Ignore OOME since we need to print "Finishing worker" for the test
    489                             // to pass.
    490                         }
    491                     }
    492                     // Keep trying to print "Finishing worker" until it succeeds.
    493                     while (true) {
    494                         try {
    495                             System.out.println("Finishing worker");
    496                             break;
    497                         } catch (OutOfMemoryError e) {
    498                         }
    499                     }
    500                 }
    501             };
    502         }
    503 
    504         // The notifier thread is a daemon just loops forever to wake
    505         // up threads in Operation.WAIT
    506         if (lock != null) {
    507             Thread notifier = new Thread("Notifier") {
    508                 public void run() {
    509                     while (true) {
    510                         synchronized (lock) {
    511                             lock.notifyAll();
    512                         }
    513                     }
    514                 }
    515             };
    516             notifier.setDaemon(true);
    517             notifier.start();
    518         }
    519 
    520         // Create and start the daemon threads.
    521         for (int r = 0; r < numberOfDaemons; r++) {
    522             Main daemon = threadStresses[numberOfThreads + r];
    523             Thread t = new Thread(daemon, "Daemon thread " + daemon.id);
    524             t.setDaemon(true);
    525             t.start();
    526         }
    527 
    528         for (int r = 0; r < runners.length; r++) {
    529             runners[r].start();
    530         }
    531         for (int r = 0; r < runners.length; r++) {
    532             runners[r].join();
    533         }
    534     }
    535 
    536     protected final Operation[] operations;
    537     private final Object lock;
    538     protected final int id;
    539 
    540     private int nextOperation;
    541 
    542     private Main(Object lock, int id, Operation[] operations) {
    543         this.lock = lock;
    544         this.id = id;
    545         this.operations = operations;
    546     }
    547 
    548     public void run() {
    549         try {
    550             if (DEBUG) {
    551                 System.out.println("Starting ThreadStress " + id);
    552             }
    553             while (nextOperation < operations.length) {
    554                 Operation operation = operations[nextOperation];
    555                 if (DEBUG) {
    556                     System.out.println("ThreadStress " + id
    557                                        + " operation " + nextOperation
    558                                        + " is " + operation);
    559                 }
    560                 nextOperation++;
    561                 if (!operation.perform()) {
    562                     return;
    563                 }
    564             }
    565         } finally {
    566             if (DEBUG) {
    567                 System.out.println("Finishing ThreadStress for " + id);
    568             }
    569         }
    570     }
    571 
    572     private static class Daemon extends Main {
    573         private Daemon(Object lock, int id, Operation[] operations) {
    574             super(lock, id, operations);
    575         }
    576 
    577         public void run() {
    578             try {
    579                 if (DEBUG) {
    580                     System.out.println("Starting ThreadStress Daemon " + id);
    581                 }
    582                 int i = 0;
    583                 while (true) {
    584                     Operation operation = operations[i];
    585                     if (DEBUG) {
    586                         System.out.println("ThreadStress Daemon " + id
    587                                            + " operation " + i
    588                                            + " is " + operation);
    589                     }
    590                     operation.perform();
    591                     i = (i + 1) % operations.length;
    592                 }
    593             } catch (OutOfMemoryError e) {
    594                 // Catch OutOfMemoryErrors since these can cause the test to fail it they print
    595                 // the stack trace after "Finishing worker".
    596             } finally {
    597                 if (DEBUG) {
    598                     System.out.println("Finishing ThreadStress Daemon for " + id);
    599                 }
    600             }
    601         }
    602     }
    603 
    604 }
    605