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          */
perform()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
perform()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
perform()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
perform()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
perform()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
perform()156         public boolean perform() {
157             Thread.currentThread().getStackTrace();
158             return true;
159         }
160     }
161 
162     private final static class Exit extends Operation {
163         @Override
perform()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
perform()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 
TimedWait(Object lock)187         public TimedWait(Object lock) {
188             this.lock = lock;
189         }
190 
191         @Override
perform()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 
Wait(Object lock)206         public Wait(Object lock) {
207             this.lock = lock;
208         }
209 
210         @Override
perform()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 
SyncAndWork(Object lock)225         public SyncAndWork(Object lock) {
226             this.lock = lock;
227         }
228 
229         @Override
perform()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 
createDefaultFrequencyMap(Object lock)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 
createLockFrequencyMap(Object lock)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 
main(String[] args)266     public static void main(String[] args) throws Exception {
267         parseAndRun(args);
268     }
269 
updateFrequencyMap(Map<Operation, Double> in, Object lock, String arg)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 
normalize(Map<Operation, Double> map)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 
parseAndRun(String[] args)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 
runTest(final int numberOfThreads, final int numberOfDaemons, final int operationsPerThread, final Object lock, Map<Operation, Double> frequencyMap)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