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 //    -o X ............ number of overall operations
36 //    -t X ............ number of operations per thread
37 //    --dumpmap ....... print the frequency map
38 //    -oom:X .......... frequency of OOM (double)
39 //    -alloc:X ........ frequency of Alloc
40 //    -stacktrace:X ... frequency of StackTrace
41 //    -exit:X ......... frequency of Exit
42 //    -sleep:X ........ frequency of Sleep
43 //    -wait:X ......... frequency of Wait
44 //    -timedwait:X .... frequency of TimedWait
45 
46 public class Main implements Runnable {
47 
48     public static final boolean DEBUG = false;
49 
50     private static abstract class Operation {
51         /**
52          * Perform the action represented by this operation. Returns true if the thread should
53          * continue.
54          */
perform()55         public abstract boolean perform();
56     }
57 
58     private final static class OOM extends Operation {
59         @Override
perform()60         public boolean perform() {
61             try {
62                 List<byte[]> l = new ArrayList<byte[]>();
63                 while (true) {
64                     l.add(new byte[1024]);
65                 }
66             } catch (OutOfMemoryError e) {
67             }
68             return true;
69         }
70     }
71 
72     private final static class SigQuit extends Operation {
73         private final static int sigquit;
74         private final static Method kill;
75         private final static int pid;
76 
77         static {
78             int pidTemp = -1;
79             int sigquitTemp = -1;
80             Method killTemp = null;
81 
82             try {
83                 Class<?> osClass = Class.forName("android.system.Os");
84                 Method getpid = osClass.getDeclaredMethod("getpid");
85                 pidTemp = (Integer)getpid.invoke(null);
86 
87                 Class<?> osConstants = Class.forName("android.system.OsConstants");
88                 Field sigquitField = osConstants.getDeclaredField("SIGQUIT");
89                 sigquitTemp = (Integer)sigquitField.get(null);
90 
91                 killTemp = osClass.getDeclaredMethod("kill", int.class, int.class);
92             } catch (Exception e) {
93                 if (!e.getClass().getName().equals("ErrnoException")) {
94                     e.printStackTrace(System.out);
95                 }
96             }
97 
98             pid = pidTemp;
99             sigquit = sigquitTemp;
100             kill = killTemp;
101         }
102 
103         @Override
perform()104         public boolean perform() {
105             try {
106                 kill.invoke(null, pid, sigquit);
107             } catch (Exception e) {
108                 if (!e.getClass().getName().equals("ErrnoException")) {
109                     e.printStackTrace(System.out);
110                 }
111             }
112             return true;
113         }
114     }
115 
116     private final static class Alloc extends Operation {
117         @Override
perform()118         public boolean perform() {
119             try {
120                 List<byte[]> l = new ArrayList<byte[]>();
121                 for (int i = 0; i < 1024; i++) {
122                     l.add(new byte[1024]);
123                 }
124             } catch (OutOfMemoryError e) {
125             }
126             return true;
127         }
128     }
129 
130     private final static class StackTrace extends Operation {
131         @Override
perform()132         public boolean perform() {
133             Thread.currentThread().getStackTrace();
134             return true;
135         }
136     }
137 
138     private final static class Exit extends Operation {
139         @Override
perform()140         public boolean perform() {
141             return false;
142         }
143     }
144 
145     private final static class Sleep extends Operation {
146         @Override
perform()147         public boolean perform() {
148             try {
149                 Thread.sleep(100);
150             } catch (InterruptedException ignored) {
151             }
152             return true;
153         }
154     }
155 
156     private final static class TimedWait extends Operation {
157         private final Object lock;
158 
TimedWait(Object lock)159         public TimedWait(Object lock) {
160             this.lock = lock;
161         }
162 
163         @Override
perform()164         public boolean perform() {
165             synchronized (lock) {
166                 try {
167                     lock.wait(100, 0);
168                 } catch (InterruptedException ignored) {
169                 }
170             }
171             return true;
172         }
173     }
174 
175     private final static class Wait extends Operation {
176         private final Object lock;
177 
Wait(Object lock)178         public Wait(Object lock) {
179             this.lock = lock;
180         }
181 
182         @Override
perform()183         public boolean perform() {
184             synchronized (lock) {
185                 try {
186                     lock.wait();
187                 } catch (InterruptedException ignored) {
188                 }
189             }
190             return true;
191         }
192     }
193 
194     private final static class SyncAndWork extends Operation {
195         private final Object lock;
196 
SyncAndWork(Object lock)197         public SyncAndWork(Object lock) {
198             this.lock = lock;
199         }
200 
201         @Override
perform()202         public boolean perform() {
203             synchronized (lock) {
204                 try {
205                     Thread.sleep((int)(Math.random()*10));
206                 } catch (InterruptedException ignored) {
207                 }
208             }
209             return true;
210         }
211     }
212 
createDefaultFrequencyMap(Object lock)213     private final static Map<Operation, Double> createDefaultFrequencyMap(Object lock) {
214         Map<Operation, Double> frequencyMap = new HashMap<Operation, Double>();
215         frequencyMap.put(new OOM(), 0.005);             //  1/200
216         frequencyMap.put(new SigQuit(), 0.095);         // 19/200
217         frequencyMap.put(new Alloc(), 0.3);             // 60/200
218         frequencyMap.put(new StackTrace(), 0.1);        // 20/200
219         frequencyMap.put(new Exit(), 0.25);             // 50/200
220         frequencyMap.put(new Sleep(), 0.125);           // 25/200
221         frequencyMap.put(new TimedWait(lock), 0.05);    // 10/200
222         frequencyMap.put(new Wait(lock), 0.075);        // 15/200
223 
224         return frequencyMap;
225     }
226 
createLockFrequencyMap(Object lock)227     private final static Map<Operation, Double> createLockFrequencyMap(Object lock) {
228       Map<Operation, Double> frequencyMap = new HashMap<Operation, Double>();
229       frequencyMap.put(new Sleep(), 0.2);
230       frequencyMap.put(new TimedWait(lock), 0.2);
231       frequencyMap.put(new Wait(lock), 0.2);
232       frequencyMap.put(new SyncAndWork(lock), 0.4);
233 
234       return frequencyMap;
235     }
236 
main(String[] args)237     public static void main(String[] args) throws Exception {
238         parseAndRun(args);
239     }
240 
updateFrequencyMap(Map<Operation, Double> in, Object lock, String arg)241     private static Map<Operation, Double> updateFrequencyMap(Map<Operation, Double> in,
242             Object lock, String arg) {
243         String split[] = arg.split(":");
244         if (split.length != 2) {
245             throw new IllegalArgumentException("Can't split argument " + arg);
246         }
247         double d;
248         try {
249             d = Double.parseDouble(split[1]);
250         } catch (Exception e) {
251             throw new IllegalArgumentException(e);
252         }
253         if (d < 0) {
254             throw new IllegalArgumentException(arg + ": value must be >= 0.");
255         }
256         Operation op = null;
257         if (split[0].equals("-oom")) {
258             op = new OOM();
259         } else if (split[0].equals("-sigquit")) {
260             op = new SigQuit();
261         } else if (split[0].equals("-alloc")) {
262             op = new Alloc();
263         } else if (split[0].equals("-stacktrace")) {
264             op = new StackTrace();
265         } else if (split[0].equals("-exit")) {
266             op = new Exit();
267         } else if (split[0].equals("-sleep")) {
268             op = new Sleep();
269         } else if (split[0].equals("-wait")) {
270             op = new Wait(lock);
271         } else if (split[0].equals("-timedwait")) {
272             op = new TimedWait(lock);
273         } else {
274             throw new IllegalArgumentException("Unknown arg " + arg);
275         }
276 
277         if (in == null) {
278             in = new HashMap<Operation, Double>();
279         }
280         in.put(op, d);
281 
282         return in;
283     }
284 
normalize(Map<Operation, Double> map)285     private static void normalize(Map<Operation, Double> map) {
286         double sum = 0;
287         for (Double d : map.values()) {
288             sum += d;
289         }
290         if (sum == 0) {
291             throw new RuntimeException("No elements!");
292         }
293         if (sum != 1.0) {
294             // Avoid ConcurrentModificationException.
295             Set<Operation> tmp = new HashSet<>(map.keySet());
296             for (Operation op : tmp) {
297                 map.put(op, map.get(op) / sum);
298             }
299         }
300     }
301 
parseAndRun(String[] args)302     public static void parseAndRun(String[] args) throws Exception {
303         int numberOfThreads = -1;
304         int totalOperations = -1;
305         int operationsPerThread = -1;
306         Object lock = new Object();
307         Map<Operation, Double> frequencyMap = null;
308         boolean dumpMap = false;
309 
310         if (args != null) {
311             for (int i = 0; i < args.length; i++) {
312                 if (args[i].equals("-n")) {
313                     i++;
314                     numberOfThreads = Integer.parseInt(args[i]);
315                 } else if (args[i].equals("-o")) {
316                     i++;
317                     totalOperations = Integer.parseInt(args[i]);
318                 } else if (args[i].equals("-t")) {
319                     i++;
320                     operationsPerThread = Integer.parseInt(args[i]);
321                 } else if (args[i].equals("--locks-only")) {
322                     lock = new Object();
323                     frequencyMap = createLockFrequencyMap(lock);
324                 } else if (args[i].equals("--dumpmap")) {
325                     dumpMap = true;
326                 } else {
327                     frequencyMap = updateFrequencyMap(frequencyMap, lock, args[i]);
328                 }
329             }
330         }
331 
332         if (totalOperations != -1 && operationsPerThread != -1) {
333             throw new IllegalArgumentException(
334                     "Specified both totalOperations and operationsPerThread");
335         }
336 
337         if (numberOfThreads == -1) {
338             numberOfThreads = 5;
339         }
340 
341         if (totalOperations == -1) {
342             totalOperations = 1000;
343         }
344 
345         if (operationsPerThread == -1) {
346             operationsPerThread = totalOperations/numberOfThreads;
347         }
348 
349         if (frequencyMap == null) {
350             frequencyMap = createDefaultFrequencyMap(lock);
351         }
352         normalize(frequencyMap);
353 
354         if (dumpMap) {
355             System.out.println(frequencyMap);
356         }
357 
358         runTest(numberOfThreads, operationsPerThread, lock, frequencyMap);
359     }
360 
runTest(final int numberOfThreads, final int operationsPerThread, final Object lock, Map<Operation, Double> frequencyMap)361     public static void runTest(final int numberOfThreads, final int operationsPerThread,
362                                final Object lock, Map<Operation, Double> frequencyMap)
363                                    throws Exception {
364         // Each thread is going to do operationsPerThread
365         // operations. The distribution of operations is determined by
366         // the Operation.frequency values. We fill out an Operation[]
367         // for each thread with the operations it is to perform. The
368         // Operation[] is shuffled so that there is more random
369         // interactions between the threads.
370 
371         // Fill in the Operation[] array for each thread by laying
372         // down references to operation according to their desired
373         // frequency.
374         final Main[] threadStresses = new Main[numberOfThreads];
375         for (int t = 0; t < threadStresses.length; t++) {
376             Operation[] operations = new Operation[operationsPerThread];
377             int o = 0;
378             LOOP:
379             while (true) {
380                 for (Operation op : frequencyMap.keySet()) {
381                     int freq = (int)(frequencyMap.get(op) * operationsPerThread);
382                     for (int f = 0; f < freq; f++) {
383                         if (o == operations.length) {
384                             break LOOP;
385                         }
386                         operations[o] = op;
387                         o++;
388                     }
389                 }
390             }
391             // Randomize the oepration order
392             Collections.shuffle(Arrays.asList(operations));
393             threadStresses[t] = new Main(lock, t, operations);
394         }
395 
396         // Enable to dump operation counts per thread to make sure its
397         // sane compared to Operation.frequency
398         if (DEBUG) {
399             for (int t = 0; t < threadStresses.length; t++) {
400                 Operation[] operations = threadStresses[t].operations;
401                 Map<Operation, Integer> distribution = new HashMap<Operation, Integer>();
402                 for (Operation operation : operations) {
403                     Integer ops = distribution.get(operation);
404                     if (ops == null) {
405                         ops = 1;
406                     } else {
407                         ops++;
408                     }
409                     distribution.put(operation, ops);
410                 }
411                 System.out.println("Distribution for " + t);
412                 for (Operation op : frequencyMap.keySet()) {
413                     System.out.println(op + " = " + distribution.get(op));
414                 }
415             }
416         }
417 
418         // Create the runners for each thread. The runner Thread
419         // ensures that thread that exit due to Operation.EXIT will be
420         // restarted until they reach their desired
421         // operationsPerThread.
422         Thread[] runners = new Thread[numberOfThreads];
423         for (int r = 0; r < runners.length; r++) {
424             final Main ts = threadStresses[r];
425             runners[r] = new Thread("Runner thread " + r) {
426                 final Main threadStress = ts;
427                 public void run() {
428                     int id = threadStress.id;
429                     System.out.println("Starting worker for " + id);
430                     while (threadStress.nextOperation < operationsPerThread) {
431                         Thread thread = new Thread(ts, "Worker thread " + id);
432                         thread.start();
433                         try {
434                             thread.join();
435                         } catch (InterruptedException e) {
436                         }
437                         System.out.println("Thread exited for " + id + " with "
438                                            + (operationsPerThread - threadStress.nextOperation)
439                                            + " operations remaining.");
440                     }
441                     System.out.println("Finishing worker");
442                 }
443             };
444         }
445 
446         // The notifier thread is a daemon just loops forever to wake
447         // up threads in Operation.WAIT
448         if (lock != null) {
449             Thread notifier = new Thread("Notifier") {
450                 public void run() {
451                     while (true) {
452                         synchronized (lock) {
453                             lock.notifyAll();
454                         }
455                     }
456                 }
457             };
458             notifier.setDaemon(true);
459             notifier.start();
460         }
461 
462         for (int r = 0; r < runners.length; r++) {
463             runners[r].start();
464         }
465         for (int r = 0; r < runners.length; r++) {
466             runners[r].join();
467         }
468     }
469 
470     private final Operation[] operations;
471     private final Object lock;
472     private final int id;
473 
474     private int nextOperation;
475 
Main(Object lock, int id, Operation[] operations)476     private Main(Object lock, int id, Operation[] operations) {
477         this.lock = lock;
478         this.id = id;
479         this.operations = operations;
480     }
481 
run()482     public void run() {
483         try {
484             if (DEBUG) {
485                 System.out.println("Starting ThreadStress " + id);
486             }
487             while (nextOperation < operations.length) {
488                 Operation operation = operations[nextOperation];
489                 if (DEBUG) {
490                     System.out.println("ThreadStress " + id
491                                        + " operation " + nextOperation
492                                        + " is " + operation);
493                 }
494                 nextOperation++;
495                 if (!operation.perform()) {
496                     return;
497                 }
498             }
499         } finally {
500             if (DEBUG) {
501                 System.out.println("Finishing ThreadStress for " + id);
502             }
503         }
504     }
505 
506 }
507