blob: 7acd950e684a5e1ae9c95dc7ea717f78a7d5c29b [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
Man Caodef3fcd2015-08-10 15:51:27 -070035// -d X ............ number of daemon threads
Andreas Gampef2fdc732014-06-11 08:20:47 -070036// -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
Andreas Gampe1c83cbc2014-07-22 18:52:29 -070047public class Main implements Runnable {
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -070048
Brian Carlstrom4514d3c2011-10-21 17:01:31 -070049 public static final boolean DEBUG = false;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -070050
Andreas Gampef2fdc732014-06-11 08:20:47 -070051 private static abstract class Operation {
52 /**
53 * Perform the action represented by this operation. Returns true if the thread should
54 * continue.
55 */
56 public abstract boolean perform();
57 }
Elliott Hughes4cd121e2013-01-07 17:35:41 -080058
Andreas Gampef2fdc732014-06-11 08:20:47 -070059 private final static class OOM extends Operation {
60 @Override
61 public boolean perform() {
62 try {
63 List<byte[]> l = new ArrayList<byte[]>();
64 while (true) {
65 l.add(new byte[1024]);
66 }
67 } catch (OutOfMemoryError e) {
68 }
69 return true;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -070070 }
71 }
72
Andreas Gampef2fdc732014-06-11 08:20:47 -070073 private final static class SigQuit extends Operation {
74 private final static int sigquit;
75 private final static Method kill;
76 private final static int pid;
77
78 static {
79 int pidTemp = -1;
80 int sigquitTemp = -1;
81 Method killTemp = null;
82
83 try {
84 Class<?> osClass = Class.forName("android.system.Os");
85 Method getpid = osClass.getDeclaredMethod("getpid");
86 pidTemp = (Integer)getpid.invoke(null);
87
88 Class<?> osConstants = Class.forName("android.system.OsConstants");
89 Field sigquitField = osConstants.getDeclaredField("SIGQUIT");
90 sigquitTemp = (Integer)sigquitField.get(null);
91
92 killTemp = osClass.getDeclaredMethod("kill", int.class, int.class);
93 } catch (Exception e) {
94 if (!e.getClass().getName().equals("ErrnoException")) {
95 e.printStackTrace(System.out);
96 }
97 }
98
99 pid = pidTemp;
100 sigquit = sigquitTemp;
101 kill = killTemp;
102 }
103
104 @Override
105 public boolean perform() {
106 try {
107 kill.invoke(null, pid, sigquit);
108 } catch (Exception e) {
109 if (!e.getClass().getName().equals("ErrnoException")) {
110 e.printStackTrace(System.out);
111 }
112 }
113 return true;
114 }
115 }
116
117 private final static class Alloc extends Operation {
118 @Override
119 public boolean perform() {
120 try {
121 List<byte[]> l = new ArrayList<byte[]>();
122 for (int i = 0; i < 1024; i++) {
123 l.add(new byte[1024]);
124 }
125 } catch (OutOfMemoryError e) {
126 }
127 return true;
128 }
129 }
130
131 private final static class StackTrace extends Operation {
132 @Override
133 public boolean perform() {
134 Thread.currentThread().getStackTrace();
135 return true;
136 }
137 }
138
139 private final static class Exit extends Operation {
140 @Override
141 public boolean perform() {
142 return false;
143 }
144 }
145
146 private final static class Sleep extends Operation {
147 @Override
148 public boolean perform() {
149 try {
150 Thread.sleep(100);
151 } catch (InterruptedException ignored) {
152 }
153 return true;
154 }
155 }
156
157 private final static class TimedWait extends Operation {
158 private final Object lock;
159
160 public TimedWait(Object lock) {
161 this.lock = lock;
162 }
163
164 @Override
165 public boolean perform() {
166 synchronized (lock) {
167 try {
168 lock.wait(100, 0);
169 } catch (InterruptedException ignored) {
170 }
171 }
172 return true;
173 }
174 }
175
176 private final static class Wait extends Operation {
177 private final Object lock;
178
179 public Wait(Object lock) {
180 this.lock = lock;
181 }
182
183 @Override
184 public boolean perform() {
185 synchronized (lock) {
186 try {
187 lock.wait();
188 } catch (InterruptedException ignored) {
189 }
190 }
191 return true;
192 }
193 }
194
195 private final static class SyncAndWork extends Operation {
196 private final Object lock;
197
198 public SyncAndWork(Object lock) {
199 this.lock = lock;
200 }
201
202 @Override
203 public boolean perform() {
204 synchronized (lock) {
205 try {
206 Thread.sleep((int)(Math.random()*10));
207 } catch (InterruptedException ignored) {
208 }
209 }
210 return true;
211 }
212 }
213
214 private final static Map<Operation, Double> createDefaultFrequencyMap(Object lock) {
215 Map<Operation, Double> frequencyMap = new HashMap<Operation, Double>();
216 frequencyMap.put(new OOM(), 0.005); // 1/200
217 frequencyMap.put(new SigQuit(), 0.095); // 19/200
218 frequencyMap.put(new Alloc(), 0.3); // 60/200
219 frequencyMap.put(new StackTrace(), 0.1); // 20/200
220 frequencyMap.put(new Exit(), 0.25); // 50/200
221 frequencyMap.put(new Sleep(), 0.125); // 25/200
222 frequencyMap.put(new TimedWait(lock), 0.05); // 10/200
223 frequencyMap.put(new Wait(lock), 0.075); // 15/200
224
225 return frequencyMap;
226 }
227
228 private final static Map<Operation, Double> createLockFrequencyMap(Object lock) {
229 Map<Operation, Double> frequencyMap = new HashMap<Operation, Double>();
230 frequencyMap.put(new Sleep(), 0.2);
231 frequencyMap.put(new TimedWait(lock), 0.2);
232 frequencyMap.put(new Wait(lock), 0.2);
233 frequencyMap.put(new SyncAndWork(lock), 0.4);
234
235 return frequencyMap;
236 }
237
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700238 public static void main(String[] args) throws Exception {
Andreas Gampef2fdc732014-06-11 08:20:47 -0700239 parseAndRun(args);
240 }
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700241
Andreas Gampef2fdc732014-06-11 08:20:47 -0700242 private static Map<Operation, Double> updateFrequencyMap(Map<Operation, Double> in,
243 Object lock, String arg) {
244 String split[] = arg.split(":");
245 if (split.length != 2) {
246 throw new IllegalArgumentException("Can't split argument " + arg);
247 }
248 double d;
249 try {
250 d = Double.parseDouble(split[1]);
251 } catch (Exception e) {
252 throw new IllegalArgumentException(e);
253 }
254 if (d < 0) {
255 throw new IllegalArgumentException(arg + ": value must be >= 0.");
256 }
257 Operation op = null;
258 if (split[0].equals("-oom")) {
259 op = new OOM();
260 } else if (split[0].equals("-sigquit")) {
261 op = new SigQuit();
262 } else if (split[0].equals("-alloc")) {
263 op = new Alloc();
264 } else if (split[0].equals("-stacktrace")) {
265 op = new StackTrace();
266 } else if (split[0].equals("-exit")) {
267 op = new Exit();
268 } else if (split[0].equals("-sleep")) {
269 op = new Sleep();
270 } else if (split[0].equals("-wait")) {
271 op = new Wait(lock);
272 } else if (split[0].equals("-timedwait")) {
273 op = new TimedWait(lock);
274 } else {
275 throw new IllegalArgumentException("Unknown arg " + arg);
276 }
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700277
Andreas Gampef2fdc732014-06-11 08:20:47 -0700278 if (in == null) {
279 in = new HashMap<Operation, Double>();
280 }
281 in.put(op, d);
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700282
Andreas Gampef2fdc732014-06-11 08:20:47 -0700283 return in;
284 }
285
286 private static void normalize(Map<Operation, Double> map) {
287 double sum = 0;
288 for (Double d : map.values()) {
289 sum += d;
290 }
291 if (sum == 0) {
292 throw new RuntimeException("No elements!");
293 }
294 if (sum != 1.0) {
295 // Avoid ConcurrentModificationException.
296 Set<Operation> tmp = new HashSet<>(map.keySet());
297 for (Operation op : tmp) {
298 map.put(op, map.get(op) / sum);
299 }
300 }
301 }
302
303 public static void parseAndRun(String[] args) throws Exception {
304 int numberOfThreads = -1;
Man Caodef3fcd2015-08-10 15:51:27 -0700305 int numberOfDaemons = -1;
Andreas Gampef2fdc732014-06-11 08:20:47 -0700306 int totalOperations = -1;
307 int operationsPerThread = -1;
308 Object lock = new Object();
309 Map<Operation, Double> frequencyMap = null;
310 boolean dumpMap = false;
311
312 if (args != null) {
313 for (int i = 0; i < args.length; i++) {
314 if (args[i].equals("-n")) {
315 i++;
316 numberOfThreads = Integer.parseInt(args[i]);
Man Caodef3fcd2015-08-10 15:51:27 -0700317 } else if (args[i].equals("-d")) {
318 i++;
319 numberOfDaemons = Integer.parseInt(args[i]);
Andreas Gampef2fdc732014-06-11 08:20:47 -0700320 } else if (args[i].equals("-o")) {
321 i++;
322 totalOperations = Integer.parseInt(args[i]);
323 } else if (args[i].equals("-t")) {
324 i++;
325 operationsPerThread = Integer.parseInt(args[i]);
326 } else if (args[i].equals("--locks-only")) {
327 lock = new Object();
328 frequencyMap = createLockFrequencyMap(lock);
329 } else if (args[i].equals("--dumpmap")) {
330 dumpMap = true;
331 } else {
332 frequencyMap = updateFrequencyMap(frequencyMap, lock, args[i]);
333 }
334 }
335 }
336
337 if (totalOperations != -1 && operationsPerThread != -1) {
338 throw new IllegalArgumentException(
339 "Specified both totalOperations and operationsPerThread");
340 }
341
342 if (numberOfThreads == -1) {
343 numberOfThreads = 5;
344 }
345
Man Caodef3fcd2015-08-10 15:51:27 -0700346 if (numberOfDaemons == -1) {
347 numberOfDaemons = 3;
348 }
349
Andreas Gampef2fdc732014-06-11 08:20:47 -0700350 if (totalOperations == -1) {
351 totalOperations = 1000;
352 }
353
354 if (operationsPerThread == -1) {
355 operationsPerThread = totalOperations/numberOfThreads;
356 }
357
358 if (frequencyMap == null) {
359 frequencyMap = createDefaultFrequencyMap(lock);
360 }
361 normalize(frequencyMap);
362
363 if (dumpMap) {
364 System.out.println(frequencyMap);
365 }
366
Man Caodef3fcd2015-08-10 15:51:27 -0700367 runTest(numberOfThreads, numberOfDaemons, operationsPerThread, lock, frequencyMap);
Andreas Gampef2fdc732014-06-11 08:20:47 -0700368 }
369
Man Caodef3fcd2015-08-10 15:51:27 -0700370 public static void runTest(final int numberOfThreads, final int numberOfDaemons,
371 final int operationsPerThread, final Object lock,
372 Map<Operation, Double> frequencyMap) throws Exception {
373 // Each normal thread is going to do operationsPerThread
374 // operations. Each daemon thread will loop over all
375 // the operations and will not stop.
376 // The distribution of operations is determined by
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700377 // the Operation.frequency values. We fill out an Operation[]
378 // for each thread with the operations it is to perform. The
379 // Operation[] is shuffled so that there is more random
380 // interactions between the threads.
381
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700382 // Fill in the Operation[] array for each thread by laying
383 // down references to operation according to their desired
384 // frequency.
Man Caodef3fcd2015-08-10 15:51:27 -0700385 // The first numberOfThreads elements are normal threads, the last
386 // numberOfDaemons elements are daemon threads.
387 final Main[] threadStresses = new Main[numberOfThreads + numberOfDaemons];
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700388 for (int t = 0; t < threadStresses.length; t++) {
389 Operation[] operations = new Operation[operationsPerThread];
390 int o = 0;
391 LOOP:
392 while (true) {
Andreas Gampef2fdc732014-06-11 08:20:47 -0700393 for (Operation op : frequencyMap.keySet()) {
394 int freq = (int)(frequencyMap.get(op) * operationsPerThread);
395 for (int f = 0; f < freq; f++) {
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700396 if (o == operations.length) {
397 break LOOP;
398 }
399 operations[o] = op;
400 o++;
401 }
402 }
403 }
Man Caodef3fcd2015-08-10 15:51:27 -0700404 // Randomize the operation order
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700405 Collections.shuffle(Arrays.asList(operations));
Man Caodef3fcd2015-08-10 15:51:27 -0700406 threadStresses[t] = t < numberOfThreads ? new Main(lock, t, operations) :
407 new Daemon(lock, t, operations);
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700408 }
409
Andreas Gampef2fdc732014-06-11 08:20:47 -0700410 // Enable to dump operation counts per thread to make sure its
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700411 // sane compared to Operation.frequency
412 if (DEBUG) {
413 for (int t = 0; t < threadStresses.length; t++) {
Andreas Gampef2fdc732014-06-11 08:20:47 -0700414 Operation[] operations = threadStresses[t].operations;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700415 Map<Operation, Integer> distribution = new HashMap<Operation, Integer>();
416 for (Operation operation : operations) {
417 Integer ops = distribution.get(operation);
418 if (ops == null) {
419 ops = 1;
420 } else {
421 ops++;
422 }
423 distribution.put(operation, ops);
424 }
425 System.out.println("Distribution for " + t);
Andreas Gampef2fdc732014-06-11 08:20:47 -0700426 for (Operation op : frequencyMap.keySet()) {
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700427 System.out.println(op + " = " + distribution.get(op));
428 }
429 }
430 }
431
432 // Create the runners for each thread. The runner Thread
433 // ensures that thread that exit due to Operation.EXIT will be
434 // restarted until they reach their desired
435 // operationsPerThread.
436 Thread[] runners = new Thread[numberOfThreads];
437 for (int r = 0; r < runners.length; r++) {
Andreas Gampe1c83cbc2014-07-22 18:52:29 -0700438 final Main ts = threadStresses[r];
Mathieu Chartier5f51d4b2013-12-03 14:24:05 -0800439 runners[r] = new Thread("Runner thread " + r) {
Andreas Gampe1c83cbc2014-07-22 18:52:29 -0700440 final Main threadStress = ts;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700441 public void run() {
442 int id = threadStress.id;
Mathieu Chartier5f51d4b2013-12-03 14:24:05 -0800443 System.out.println("Starting worker for " + id);
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700444 while (threadStress.nextOperation < operationsPerThread) {
Mathieu Chartier5f51d4b2013-12-03 14:24:05 -0800445 Thread thread = new Thread(ts, "Worker thread " + id);
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700446 thread.start();
447 try {
448 thread.join();
449 } catch (InterruptedException e) {
450 }
451 System.out.println("Thread exited for " + id + " with "
452 + (operationsPerThread - threadStress.nextOperation)
453 + " operations remaining.");
454 }
Andreas Gampe1c83cbc2014-07-22 18:52:29 -0700455 System.out.println("Finishing worker");
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700456 }
457 };
458 }
459
460 // The notifier thread is a daemon just loops forever to wake
461 // up threads in Operation.WAIT
Andreas Gampef2fdc732014-06-11 08:20:47 -0700462 if (lock != null) {
463 Thread notifier = new Thread("Notifier") {
464 public void run() {
465 while (true) {
466 synchronized (lock) {
467 lock.notifyAll();
468 }
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700469 }
470 }
Andreas Gampef2fdc732014-06-11 08:20:47 -0700471 };
472 notifier.setDaemon(true);
473 notifier.start();
474 }
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700475
Man Caodef3fcd2015-08-10 15:51:27 -0700476 // Create and start the daemon threads.
477 for (int r = 0; r < numberOfDaemons; r++) {
478 Main daemon = threadStresses[numberOfThreads + r];
479 Thread t = new Thread(daemon, "Daemon thread " + daemon.id);
480 t.setDaemon(true);
481 t.start();
482 }
483
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700484 for (int r = 0; r < runners.length; r++) {
485 runners[r].start();
486 }
487 for (int r = 0; r < runners.length; r++) {
488 runners[r].join();
489 }
490 }
491
Man Caodef3fcd2015-08-10 15:51:27 -0700492 protected final Operation[] operations;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700493 private final Object lock;
Man Caodef3fcd2015-08-10 15:51:27 -0700494 protected final int id;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700495
496 private int nextOperation;
497
Andreas Gampe1c83cbc2014-07-22 18:52:29 -0700498 private Main(Object lock, int id, Operation[] operations) {
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700499 this.lock = lock;
500 this.id = id;
501 this.operations = operations;
502 }
503
504 public void run() {
505 try {
506 if (DEBUG) {
507 System.out.println("Starting ThreadStress " + id);
508 }
509 while (nextOperation < operations.length) {
510 Operation operation = operations[nextOperation];
511 if (DEBUG) {
512 System.out.println("ThreadStress " + id
513 + " operation " + nextOperation
514 + " is " + operation);
515 }
516 nextOperation++;
Andreas Gampef2fdc732014-06-11 08:20:47 -0700517 if (!operation.perform()) {
518 return;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700519 }
520 }
Brian Carlstrom4514d3c2011-10-21 17:01:31 -0700521 } finally {
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700522 if (DEBUG) {
523 System.out.println("Finishing ThreadStress for " + id);
524 }
525 }
526 }
Andreas Gampe1c83cbc2014-07-22 18:52:29 -0700527
Man Caodef3fcd2015-08-10 15:51:27 -0700528 private static class Daemon extends Main {
529 private Daemon(Object lock, int id, Operation[] operations) {
530 super(lock, id, operations);
531 }
532
533 public void run() {
534 try {
535 if (DEBUG) {
536 System.out.println("Starting ThreadStress Daemon " + id);
537 }
538 int i = 0;
539 while (true) {
540 Operation operation = operations[i];
541 if (DEBUG) {
542 System.out.println("ThreadStress Daemon " + id
543 + " operation " + i
544 + " is " + operation);
545 }
546 operation.perform();
547 i = (i + 1) % operations.length;
548 }
Mathieu Chartierbf815472015-08-13 13:02:01 -0700549 } catch (OutOfMemoryError e) {
550 // Catch OutOfMemoryErrors since these can cause the test to fail it they print
551 // the stack trace after "Finishing worker".
Man Caodef3fcd2015-08-10 15:51:27 -0700552 } finally {
553 if (DEBUG) {
554 System.out.println("Finishing ThreadStress Daemon for " + id);
555 }
556 }
557 }
558 }
559
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700560}