blob: 9461c0b967509e845c3a92d17b9b3e5c59d9e2d7 [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) {
Mathieu Chartier031768a2015-08-27 10:25:02 -0700313 // args[0] is libarttest
314 for (int i = 1; i < args.length; i++) {
Andreas Gampef2fdc732014-06-11 08:20:47 -0700315 if (args[i].equals("-n")) {
316 i++;
317 numberOfThreads = Integer.parseInt(args[i]);
Man Caodef3fcd2015-08-10 15:51:27 -0700318 } else if (args[i].equals("-d")) {
319 i++;
320 numberOfDaemons = Integer.parseInt(args[i]);
Andreas Gampef2fdc732014-06-11 08:20:47 -0700321 } else if (args[i].equals("-o")) {
322 i++;
323 totalOperations = Integer.parseInt(args[i]);
324 } else if (args[i].equals("-t")) {
325 i++;
326 operationsPerThread = Integer.parseInt(args[i]);
327 } else if (args[i].equals("--locks-only")) {
328 lock = new Object();
329 frequencyMap = createLockFrequencyMap(lock);
330 } else if (args[i].equals("--dumpmap")) {
331 dumpMap = true;
332 } else {
333 frequencyMap = updateFrequencyMap(frequencyMap, lock, args[i]);
334 }
335 }
336 }
337
338 if (totalOperations != -1 && operationsPerThread != -1) {
339 throw new IllegalArgumentException(
340 "Specified both totalOperations and operationsPerThread");
341 }
342
343 if (numberOfThreads == -1) {
344 numberOfThreads = 5;
345 }
346
Man Caodef3fcd2015-08-10 15:51:27 -0700347 if (numberOfDaemons == -1) {
348 numberOfDaemons = 3;
349 }
350
Andreas Gampef2fdc732014-06-11 08:20:47 -0700351 if (totalOperations == -1) {
352 totalOperations = 1000;
353 }
354
355 if (operationsPerThread == -1) {
356 operationsPerThread = totalOperations/numberOfThreads;
357 }
358
359 if (frequencyMap == null) {
360 frequencyMap = createDefaultFrequencyMap(lock);
361 }
362 normalize(frequencyMap);
363
364 if (dumpMap) {
365 System.out.println(frequencyMap);
366 }
367
Man Caodef3fcd2015-08-10 15:51:27 -0700368 runTest(numberOfThreads, numberOfDaemons, operationsPerThread, lock, frequencyMap);
Andreas Gampef2fdc732014-06-11 08:20:47 -0700369 }
370
Man Caodef3fcd2015-08-10 15:51:27 -0700371 public static void runTest(final int numberOfThreads, final int numberOfDaemons,
372 final int operationsPerThread, final Object lock,
373 Map<Operation, Double> frequencyMap) throws Exception {
374 // Each normal thread is going to do operationsPerThread
375 // operations. Each daemon thread will loop over all
376 // the operations and will not stop.
377 // The distribution of operations is determined by
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700378 // the Operation.frequency values. We fill out an Operation[]
379 // for each thread with the operations it is to perform. The
380 // Operation[] is shuffled so that there is more random
381 // interactions between the threads.
382
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700383 // Fill in the Operation[] array for each thread by laying
384 // down references to operation according to their desired
385 // frequency.
Man Caodef3fcd2015-08-10 15:51:27 -0700386 // The first numberOfThreads elements are normal threads, the last
387 // numberOfDaemons elements are daemon threads.
388 final Main[] threadStresses = new Main[numberOfThreads + numberOfDaemons];
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700389 for (int t = 0; t < threadStresses.length; t++) {
390 Operation[] operations = new Operation[operationsPerThread];
391 int o = 0;
392 LOOP:
393 while (true) {
Andreas Gampef2fdc732014-06-11 08:20:47 -0700394 for (Operation op : frequencyMap.keySet()) {
395 int freq = (int)(frequencyMap.get(op) * operationsPerThread);
396 for (int f = 0; f < freq; f++) {
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700397 if (o == operations.length) {
398 break LOOP;
399 }
400 operations[o] = op;
401 o++;
402 }
403 }
404 }
Man Caodef3fcd2015-08-10 15:51:27 -0700405 // Randomize the operation order
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700406 Collections.shuffle(Arrays.asList(operations));
Man Caodef3fcd2015-08-10 15:51:27 -0700407 threadStresses[t] = t < numberOfThreads ? new Main(lock, t, operations) :
408 new Daemon(lock, t, operations);
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700409 }
410
Andreas Gampef2fdc732014-06-11 08:20:47 -0700411 // Enable to dump operation counts per thread to make sure its
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700412 // sane compared to Operation.frequency
413 if (DEBUG) {
414 for (int t = 0; t < threadStresses.length; t++) {
Andreas Gampef2fdc732014-06-11 08:20:47 -0700415 Operation[] operations = threadStresses[t].operations;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700416 Map<Operation, Integer> distribution = new HashMap<Operation, Integer>();
417 for (Operation operation : operations) {
418 Integer ops = distribution.get(operation);
419 if (ops == null) {
420 ops = 1;
421 } else {
422 ops++;
423 }
424 distribution.put(operation, ops);
425 }
426 System.out.println("Distribution for " + t);
Andreas Gampef2fdc732014-06-11 08:20:47 -0700427 for (Operation op : frequencyMap.keySet()) {
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700428 System.out.println(op + " = " + distribution.get(op));
429 }
430 }
431 }
432
433 // Create the runners for each thread. The runner Thread
434 // ensures that thread that exit due to Operation.EXIT will be
435 // restarted until they reach their desired
436 // operationsPerThread.
437 Thread[] runners = new Thread[numberOfThreads];
438 for (int r = 0; r < runners.length; r++) {
Andreas Gampe1c83cbc2014-07-22 18:52:29 -0700439 final Main ts = threadStresses[r];
Mathieu Chartier5f51d4b2013-12-03 14:24:05 -0800440 runners[r] = new Thread("Runner thread " + r) {
Andreas Gampe1c83cbc2014-07-22 18:52:29 -0700441 final Main threadStress = ts;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700442 public void run() {
443 int id = threadStress.id;
Mathieu Chartier5f51d4b2013-12-03 14:24:05 -0800444 System.out.println("Starting worker for " + id);
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700445 while (threadStress.nextOperation < operationsPerThread) {
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700446 try {
Mathieu Chartier72e36d82015-09-17 20:46:56 -0700447 Thread thread = new Thread(ts, "Worker thread " + id);
448 thread.start();
449 try {
450 thread.join();
451 } catch (InterruptedException e) {
452 }
453
Mathieu Chartier9d3c3fc2015-08-18 11:42:03 -0700454 System.out.println("Thread exited for " + id + " with "
455 + (operationsPerThread - threadStress.nextOperation)
456 + " operations remaining.");
457 } catch (OutOfMemoryError e) {
458 // Ignore OOME since we need to print "Finishing worker" for the test
459 // to pass.
460 }
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700461 }
Mathieu Chartier72e36d82015-09-17 20:46:56 -0700462 // Keep trying to print "Finishing worker" until it succeeds.
463 while (true) {
464 try {
465 System.out.println("Finishing worker");
466 break;
467 } catch (OutOfMemoryError e) {
468 }
469 }
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700470 }
471 };
472 }
473
474 // The notifier thread is a daemon just loops forever to wake
475 // up threads in Operation.WAIT
Andreas Gampef2fdc732014-06-11 08:20:47 -0700476 if (lock != null) {
477 Thread notifier = new Thread("Notifier") {
478 public void run() {
479 while (true) {
480 synchronized (lock) {
481 lock.notifyAll();
482 }
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700483 }
484 }
Andreas Gampef2fdc732014-06-11 08:20:47 -0700485 };
486 notifier.setDaemon(true);
487 notifier.start();
488 }
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700489
Man Caodef3fcd2015-08-10 15:51:27 -0700490 // Create and start the daemon threads.
491 for (int r = 0; r < numberOfDaemons; r++) {
492 Main daemon = threadStresses[numberOfThreads + r];
493 Thread t = new Thread(daemon, "Daemon thread " + daemon.id);
494 t.setDaemon(true);
495 t.start();
496 }
497
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700498 for (int r = 0; r < runners.length; r++) {
499 runners[r].start();
500 }
501 for (int r = 0; r < runners.length; r++) {
502 runners[r].join();
503 }
504 }
505
Man Caodef3fcd2015-08-10 15:51:27 -0700506 protected final Operation[] operations;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700507 private final Object lock;
Man Caodef3fcd2015-08-10 15:51:27 -0700508 protected final int id;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700509
510 private int nextOperation;
511
Andreas Gampe1c83cbc2014-07-22 18:52:29 -0700512 private Main(Object lock, int id, Operation[] operations) {
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700513 this.lock = lock;
514 this.id = id;
515 this.operations = operations;
516 }
517
518 public void run() {
519 try {
520 if (DEBUG) {
521 System.out.println("Starting ThreadStress " + id);
522 }
523 while (nextOperation < operations.length) {
524 Operation operation = operations[nextOperation];
525 if (DEBUG) {
526 System.out.println("ThreadStress " + id
527 + " operation " + nextOperation
528 + " is " + operation);
529 }
530 nextOperation++;
Andreas Gampef2fdc732014-06-11 08:20:47 -0700531 if (!operation.perform()) {
532 return;
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700533 }
534 }
Brian Carlstrom4514d3c2011-10-21 17:01:31 -0700535 } finally {
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700536 if (DEBUG) {
537 System.out.println("Finishing ThreadStress for " + id);
538 }
539 }
540 }
Andreas Gampe1c83cbc2014-07-22 18:52:29 -0700541
Man Caodef3fcd2015-08-10 15:51:27 -0700542 private static class Daemon extends Main {
543 private Daemon(Object lock, int id, Operation[] operations) {
544 super(lock, id, operations);
545 }
546
547 public void run() {
548 try {
549 if (DEBUG) {
550 System.out.println("Starting ThreadStress Daemon " + id);
551 }
552 int i = 0;
553 while (true) {
554 Operation operation = operations[i];
555 if (DEBUG) {
556 System.out.println("ThreadStress Daemon " + id
557 + " operation " + i
558 + " is " + operation);
559 }
560 operation.perform();
561 i = (i + 1) % operations.length;
562 }
Mathieu Chartierbf815472015-08-13 13:02:01 -0700563 } catch (OutOfMemoryError e) {
564 // Catch OutOfMemoryErrors since these can cause the test to fail it they print
565 // the stack trace after "Finishing worker".
Man Caodef3fcd2015-08-10 15:51:27 -0700566 } finally {
567 if (DEBUG) {
568 System.out.println("Finishing ThreadStress Daemon for " + id);
569 }
570 }
571 }
572 }
573
Brian Carlstrom7c6deaa2011-10-21 12:05:06 -0700574}