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.List;
23 import java.util.Map;
24 
25 // Run on host with:
26 //   javac ThreadTest.java && java ThreadStress && rm *.class
27 public class Main implements Runnable {
28 
29     public static final boolean DEBUG = false;
30 
31     enum Operation {
32         OOM(1),
33         SIGQUIT(19),
34         ALLOC(60),
35         STACKTRACE(20),
36         EXIT(50),
37 
38         SLEEP(25),
39         TIMED_WAIT(10),
40         WAIT(15);
41 
42         private final int frequency;
Operation(int frequency)43         Operation(int frequency) {
44             this.frequency = frequency;
45         }
46     }
47 
main(String[] args)48     public static void main(String[] args) throws Exception {
49 
50         final int numberOfThreads = 5;
51         final int totalOperations = 1000;
52         final int operationsPerThread = totalOperations/numberOfThreads;
53 
54         // Lock used to notify threads performing Operation.WAIT
55         final Object lock = new Object();
56 
57         // Each thread is going to do operationsPerThread
58         // operations. The distribution of operations is determined by
59         // the Operation.frequency values. We fill out an Operation[]
60         // for each thread with the operations it is to perform. The
61         // Operation[] is shuffled so that there is more random
62         // interactions between the threads.
63 
64         // The simple-minded filling in of Operation[] based on
65         // Operation.frequency below won't have even have close to a
66         // reasonable distribution if the count of Operation
67         // frequencies is greater than the total number of
68         // operations. So here we do a quick sanity check in case
69         // people tweak the constants above.
70         int operationCount = 0;
71         for (Operation op : Operation.values()) {
72             operationCount += op.frequency;
73         }
74         if (operationCount > operationsPerThread) {
75             throw new AssertionError(operationCount + " > " + operationsPerThread);
76         }
77 
78         // Fill in the Operation[] array for each thread by laying
79         // down references to operation according to their desired
80         // frequency.
81         final Main[] threadStresses = new Main[numberOfThreads];
82         for (int t = 0; t < threadStresses.length; t++) {
83             Operation[] operations = new Operation[operationsPerThread];
84             int o = 0;
85             LOOP:
86             while (true) {
87                 for (Operation op : Operation.values()) {
88                     for (int f = 0; f < op.frequency; f++) {
89                         if (o == operations.length) {
90                             break LOOP;
91                         }
92                         operations[o] = op;
93                         o++;
94                     }
95                 }
96             }
97             // Randomize the oepration order
98             Collections.shuffle(Arrays.asList(operations));
99             threadStresses[t] = new Main(lock, t, operations);
100         }
101 
102         // Enable to dump operation counds per thread to make sure its
103         // sane compared to Operation.frequency
104         if (DEBUG) {
105             for (int t = 0; t < threadStresses.length; t++) {
106                 Operation[] operations = new Operation[operationsPerThread];
107                 Map<Operation, Integer> distribution = new HashMap<Operation, Integer>();
108                 for (Operation operation : operations) {
109                     Integer ops = distribution.get(operation);
110                     if (ops == null) {
111                         ops = 1;
112                     } else {
113                         ops++;
114                     }
115                     distribution.put(operation, ops);
116                 }
117                 System.out.println("Distribution for " + t);
118                 for (Operation op : Operation.values()) {
119                     System.out.println(op + " = " + distribution.get(op));
120                 }
121             }
122         }
123 
124         // Create the runners for each thread. The runner Thread
125         // ensures that thread that exit due to Operation.EXIT will be
126         // restarted until they reach their desired
127         // operationsPerThread.
128         Thread[] runners = new Thread[numberOfThreads];
129         for (int r = 0; r < runners.length; r++) {
130             final Main ts = threadStresses[r];
131             runners[r] = new Thread("Runner thread " + r) {
132                 final Main threadStress = ts;
133                 public void run() {
134                     int id = threadStress.id;
135                     System.out.println("Starting worker for " + id);
136                     while (threadStress.nextOperation < operationsPerThread) {
137                         Thread thread = new Thread(ts, "Worker thread " + id);
138                         thread.start();
139                         try {
140                             thread.join();
141                         } catch (InterruptedException e) {
142                         }
143                         System.out.println("Thread exited for " + id + " with "
144                                            + (operationsPerThread - threadStress.nextOperation)
145                                            + " operations remaining.");
146                     }
147                     System.out.println("Finishing worker");
148                 }
149             };
150         }
151 
152         // The notifier thread is a daemon just loops forever to wake
153         // up threads in Operation.WAIT
154         Thread notifier = new Thread("Notifier") {
155             public void run() {
156                 while (true) {
157                     synchronized (lock) {
158                         lock.notifyAll();
159                     }
160                 }
161             }
162         };
163         notifier.setDaemon(true);
164         notifier.start();
165 
166         for (int r = 0; r < runners.length; r++) {
167             runners[r].start();
168         }
169         for (int r = 0; r < runners.length; r++) {
170             runners[r].join();
171         }
172     }
173 
174     private final Operation[] operations;
175     private final Object lock;
176     private final int id;
177 
178     private int nextOperation;
179 
Main(Object lock, int id, Operation[] operations)180     private Main(Object lock, int id, Operation[] operations) {
181         this.lock = lock;
182         this.id = id;
183         this.operations = operations;
184     }
185 
run()186     public void run() {
187         try {
188             if (DEBUG) {
189                 System.out.println("Starting ThreadStress " + id);
190             }
191             while (nextOperation < operations.length) {
192                 Operation operation = operations[nextOperation];
193                 if (DEBUG) {
194                     System.out.println("ThreadStress " + id
195                                        + " operation " + nextOperation
196                                        + " is " + operation);
197                 }
198                 nextOperation++;
199                 switch (operation) {
200                     case EXIT: {
201                         return;
202                     }
203                     case SIGQUIT: {
204                         try {
205                             SIGQUIT();
206                         } catch (Exception ex) {
207                         }
208                     }
209                     case SLEEP: {
210                         try {
211                             Thread.sleep(100);
212                         } catch (InterruptedException ignored) {
213                         }
214                     }
215                     case TIMED_WAIT: {
216                         synchronized (lock) {
217                             try {
218                                 lock.wait(100, 0);
219                             } catch (InterruptedException ignored) {
220                             }
221                         }
222                         break;
223                     }
224                     case WAIT: {
225                         synchronized (lock) {
226                             try {
227                                 lock.wait();
228                             } catch (InterruptedException ignored) {
229                             }
230                         }
231                         break;
232                     }
233                     case OOM: {
234                         try {
235                             List<byte[]> l = new ArrayList<byte[]>();
236                             while (true) {
237                                 l.add(new byte[1024]);
238                             }
239                         } catch (OutOfMemoryError e) {
240                         }
241                         break;
242                     }
243                     case ALLOC: {
244                         try {
245                             List<byte[]> l = new ArrayList<byte[]>();
246                             for (int i = 0; i < 1024; i++) {
247                                 l.add(new byte[1024]);
248                             }
249                         } catch (OutOfMemoryError e) {
250                         }
251                         break;
252                     }
253                     case STACKTRACE: {
254                         Thread.currentThread().getStackTrace();
255                         break;
256                     }
257                     default: {
258                         throw new AssertionError(operation.toString());
259                     }
260                 }
261             }
262         } finally {
263             if (DEBUG) {
264                 System.out.println("Finishing ThreadStress for " + id);
265             }
266         }
267     }
268 
SIGQUIT()269     private static void SIGQUIT() throws Exception {
270         Class<?> osClass = Class.forName("android.system.Os");
271         Method getpid = osClass.getDeclaredMethod("getpid");
272         int pid = (Integer)getpid.invoke(null);
273 
274         Class<?> osConstants = Class.forName("android.system.OsConstants");
275         Field sigquitField = osConstants.getDeclaredField("SIGQUIT");
276         int sigquit = (Integer)sigquitField.get(null);
277 
278         Method kill = osClass.getDeclaredMethod("kill", int.class, int.class);
279         kill.invoke(null, pid, sigquit);
280     }
281 }
282