blob: 6e7d5b665807f0191237954bb0b1cc93788d9796 [file] [log] [blame]
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -07001/*
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
Andreas Gampe1c83cbc2014-07-22 18:52:29 -070017import java.lang.reflect.*;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -070018import java.util.ArrayList;
19import java.util.Arrays;
20import java.util.Collections;
21import java.util.HashMap;
Andreas Gampef2fdc732014-06-11 08:20:47 -070022import java.util.HashSet;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -070023import java.util.List;
24import java.util.Map;
Andreas Gampef2fdc732014-06-11 08:20:47 -070025import java.util.Set;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -070026
27// Run on host with:
28// javac ThreadTest.java && java ThreadStress && rm *.class
Andreas Gampef2fdc732014-06-11 08:20:47 -070029// 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
Andreas Gampe1c83cbc2014-07-22 18:52:29 -070046public class Main implements Runnable {
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -070047
Brian Carlstrom4514d3c2011-10-21 17:01:31 -070048 public static final boolean DEBUG = false;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -070049
Andreas Gampef2fdc732014-06-11 08:20:47 -070050 private static abstract class Operation {
51 /**
52 * Perform the action represented by this operation. Returns true if the thread should
53 * continue.
54 */
55 public abstract boolean perform();
56 }
Elliott Hughes4cd121e2013-01-07 17:35:41 -080057
Andreas Gampef2fdc732014-06-11 08:20:47 -070058 private final static class OOM extends Operation {
59 @Override
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;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -070069 }
70 }
71
Andreas Gampef2fdc732014-06-11 08:20:47 -070072 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
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
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
132 public boolean perform() {
133 Thread.currentThread().getStackTrace();
134 return true;
135 }
136 }
137
138 private final static class Exit extends Operation {
139 @Override
140 public boolean perform() {
141 return false;
142 }
143 }
144
145 private final static class Sleep extends Operation {
146 @Override
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
159 public TimedWait(Object lock) {
160 this.lock = lock;
161 }
162
163 @Override
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
178 public Wait(Object lock) {
179 this.lock = lock;
180 }
181
182 @Override
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
197 public SyncAndWork(Object lock) {
198 this.lock = lock;
199 }
200
201 @Override
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
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
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
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700237 public static void main(String[] args) throws Exception {
Andreas Gampef2fdc732014-06-11 08:20:47 -0700238 parseAndRun(args);
239 }
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700240
Andreas Gampef2fdc732014-06-11 08:20:47 -0700241 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 }
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700276
Andreas Gampef2fdc732014-06-11 08:20:47 -0700277 if (in == null) {
278 in = new HashMap<Operation, Double>();
279 }
280 in.put(op, d);
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700281
Andreas Gampef2fdc732014-06-11 08:20:47 -0700282 return in;
283 }
284
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
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
361 public static void runTest(final int numberOfThreads, final int operationsPerThread,
362 final Object lock, Map<Operation, Double> frequencyMap)
363 throws Exception {
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700364 // 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
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700371 // Fill in the Operation[] array for each thread by laying
372 // down references to operation according to their desired
373 // frequency.
Andreas Gampe1c83cbc2014-07-22 18:52:29 -0700374 final Main[] threadStresses = new Main[numberOfThreads];
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700375 for (int t = 0; t < threadStresses.length; t++) {
376 Operation[] operations = new Operation[operationsPerThread];
377 int o = 0;
378 LOOP:
379 while (true) {
Andreas Gampef2fdc732014-06-11 08:20:47 -0700380 for (Operation op : frequencyMap.keySet()) {
381 int freq = (int)(frequencyMap.get(op) * operationsPerThread);
382 for (int f = 0; f < freq; f++) {
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700383 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));
Andreas Gampe1c83cbc2014-07-22 18:52:29 -0700393 threadStresses[t] = new Main(lock, t, operations);
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700394 }
395
Andreas Gampef2fdc732014-06-11 08:20:47 -0700396 // Enable to dump operation counts per thread to make sure its
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700397 // sane compared to Operation.frequency
398 if (DEBUG) {
399 for (int t = 0; t < threadStresses.length; t++) {
Andreas Gampef2fdc732014-06-11 08:20:47 -0700400 Operation[] operations = threadStresses[t].operations;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700401 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);
Andreas Gampef2fdc732014-06-11 08:20:47 -0700412 for (Operation op : frequencyMap.keySet()) {
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700413 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++) {
Andreas Gampe1c83cbc2014-07-22 18:52:29 -0700424 final Main ts = threadStresses[r];
Mathieu Chartier5f51d4b2013-12-03 14:24:05 -0800425 runners[r] = new Thread("Runner thread " + r) {
Andreas Gampe1c83cbc2014-07-22 18:52:29 -0700426 final Main threadStress = ts;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700427 public void run() {
428 int id = threadStress.id;
Mathieu Chartier5f51d4b2013-12-03 14:24:05 -0800429 System.out.println("Starting worker for " + id);
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700430 while (threadStress.nextOperation < operationsPerThread) {
Mathieu Chartier5f51d4b2013-12-03 14:24:05 -0800431 Thread thread = new Thread(ts, "Worker thread " + id);
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700432 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 }
Andreas Gampe1c83cbc2014-07-22 18:52:29 -0700441 System.out.println("Finishing worker");
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700442 }
443 };
444 }
445
446 // The notifier thread is a daemon just loops forever to wake
447 // up threads in Operation.WAIT
Andreas Gampef2fdc732014-06-11 08:20:47 -0700448 if (lock != null) {
449 Thread notifier = new Thread("Notifier") {
450 public void run() {
451 while (true) {
452 synchronized (lock) {
453 lock.notifyAll();
454 }
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700455 }
456 }
Andreas Gampef2fdc732014-06-11 08:20:47 -0700457 };
458 notifier.setDaemon(true);
459 notifier.start();
460 }
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700461
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
Andreas Gampe1c83cbc2014-07-22 18:52:29 -0700476 private Main(Object lock, int id, Operation[] operations) {
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700477 this.lock = lock;
478 this.id = id;
479 this.operations = operations;
480 }
481
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++;
Andreas Gampef2fdc732014-06-11 08:20:47 -0700495 if (!operation.perform()) {
496 return;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700497 }
498 }
Brian Carlstrom4514d3c2011-10-21 17:01:31 -0700499 } finally {
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700500 if (DEBUG) {
501 System.out.println("Finishing ThreadStress for " + id);
502 }
503 }
504 }
Andreas Gampe1c83cbc2014-07-22 18:52:29 -0700505
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700506}