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
17import java.lang.reflect.*;
18import java.util.ArrayList;
19import java.util.Arrays;
20import java.util.Collections;
21import java.util.HashMap;
22import java.util.List;
23import java.util.Map;
24
25// Run on host with:
26//   javac ThreadTest.java && java ThreadStress && rm *.class
27public 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;
43        Operation(int frequency) {
44            this.frequency = frequency;
45        }
46    }
47
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
180    private Main(Object lock, int id, Operation[] operations) {
181        this.lock = lock;
182        this.id = id;
183        this.operations = operations;
184    }
185
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
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