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.HashSet; 23 import java.util.List; 24 import java.util.Map; 25 import java.util.Set; 26 27 // Run on host with: 28 // javac ThreadTest.java && java ThreadStress && rm *.class 29 // 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 // -d X ............ number of daemon threads 36 // -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 47 public class Main implements Runnable { 48 49 public static final boolean DEBUG = false; 50 51 private static abstract class Operation { 52 /** 53 * Perform the action represented by this operation. Returns true if the thread should 54 * continue. 55 */ perform()56 public abstract boolean perform(); 57 } 58 59 private final static class OOM extends Operation { 60 private final static int ALLOC_SIZE = 1024; 61 62 @Override perform()63 public boolean perform() { 64 try { 65 List<byte[]> l = new ArrayList<byte[]>(); 66 while (true) { 67 l.add(new byte[ALLOC_SIZE]); 68 } 69 } catch (OutOfMemoryError e) { 70 } 71 return true; 72 } 73 } 74 75 private final static class SigQuit extends Operation { 76 private final static int sigquit; 77 private final static Method kill; 78 private final static int pid; 79 80 static { 81 int pidTemp = -1; 82 int sigquitTemp = -1; 83 Method killTemp = null; 84 85 try { 86 Class<?> osClass = Class.forName("android.system.Os"); 87 Method getpid = osClass.getDeclaredMethod("getpid"); 88 pidTemp = (Integer)getpid.invoke(null); 89 90 Class<?> osConstants = Class.forName("android.system.OsConstants"); 91 Field sigquitField = osConstants.getDeclaredField("SIGQUIT"); 92 sigquitTemp = (Integer)sigquitField.get(null); 93 94 killTemp = osClass.getDeclaredMethod("kill", int.class, int.class); 95 } catch (Exception e) { 96 Main.printThrowable(e); 97 } 98 99 pid = pidTemp; 100 sigquit = sigquitTemp; 101 kill = killTemp; 102 } 103 104 @Override perform()105 public boolean perform() { 106 try { 107 kill.invoke(null, pid, sigquit); 108 } catch (OutOfMemoryError e) { 109 } catch (Exception e) { 110 if (!e.getClass().getName().equals(Main.errnoExceptionName)) { 111 Main.printThrowable(e); 112 } 113 } 114 return true; 115 } 116 } 117 118 private final static class Alloc extends Operation { 119 private final static int ALLOC_SIZE = 1024; // Needs to be small enough to not be in LOS. 120 private final static int ALLOC_COUNT = 1024; 121 122 @Override perform()123 public boolean perform() { 124 try { 125 List<byte[]> l = new ArrayList<byte[]>(); 126 for (int i = 0; i < ALLOC_COUNT; i++) { 127 l.add(new byte[ALLOC_SIZE]); 128 } 129 } catch (OutOfMemoryError e) { 130 } 131 return true; 132 } 133 } 134 135 private final static class LargeAlloc extends Operation { 136 private final static int PAGE_SIZE = 4096; 137 private final static int PAGE_SIZE_MODIFIER = 10; // Needs to be large enough for LOS. 138 private final static int ALLOC_COUNT = 100; 139 140 @Override perform()141 public boolean perform() { 142 try { 143 List<byte[]> l = new ArrayList<byte[]>(); 144 for (int i = 0; i < ALLOC_COUNT; i++) { 145 l.add(new byte[PAGE_SIZE_MODIFIER * PAGE_SIZE]); 146 } 147 } catch (OutOfMemoryError e) { 148 } 149 return true; 150 } 151 } 152 153 private final static class StackTrace extends Operation { 154 @Override perform()155 public boolean perform() { 156 try { 157 Thread.currentThread().getStackTrace(); 158 } catch (OutOfMemoryError e) { 159 } 160 return true; 161 } 162 } 163 164 private final static class Exit extends Operation { 165 @Override perform()166 public boolean perform() { 167 return false; 168 } 169 } 170 171 private final static class Sleep extends Operation { 172 private final static int SLEEP_TIME = 100; 173 174 @Override perform()175 public boolean perform() { 176 try { 177 Thread.sleep(SLEEP_TIME); 178 } catch (InterruptedException ignored) { 179 } 180 return true; 181 } 182 } 183 184 private final static class TimedWait extends Operation { 185 private final static int SLEEP_TIME = 100; 186 187 private final Object lock; 188 TimedWait(Object lock)189 public TimedWait(Object lock) { 190 this.lock = lock; 191 } 192 193 @Override perform()194 public boolean perform() { 195 synchronized (lock) { 196 try { 197 lock.wait(SLEEP_TIME, 0); 198 } catch (InterruptedException ignored) { 199 } 200 } 201 return true; 202 } 203 } 204 205 private final static class Wait extends Operation { 206 private final Object lock; 207 Wait(Object lock)208 public Wait(Object lock) { 209 this.lock = lock; 210 } 211 212 @Override perform()213 public boolean perform() { 214 synchronized (lock) { 215 try { 216 lock.wait(); 217 } catch (InterruptedException ignored) { 218 } 219 } 220 return true; 221 } 222 } 223 224 private final static class SyncAndWork extends Operation { 225 private final Object lock; 226 SyncAndWork(Object lock)227 public SyncAndWork(Object lock) { 228 this.lock = lock; 229 } 230 231 @Override perform()232 public boolean perform() { 233 synchronized (lock) { 234 try { 235 Thread.sleep((int)(Math.random() * 50 + 50)); 236 } catch (InterruptedException ignored) { 237 } 238 } 239 return true; 240 } 241 } 242 createDefaultFrequencyMap(Object lock)243 private final static Map<Operation, Double> createDefaultFrequencyMap(Object lock) { 244 Map<Operation, Double> frequencyMap = new HashMap<Operation, Double>(); 245 frequencyMap.put(new OOM(), 0.005); // 1/200 246 frequencyMap.put(new SigQuit(), 0.095); // 19/200 247 frequencyMap.put(new Alloc(), 0.25); // 50/200 248 frequencyMap.put(new LargeAlloc(), 0.05); // 10/200 249 frequencyMap.put(new StackTrace(), 0.1); // 20/200 250 frequencyMap.put(new Exit(), 0.25); // 50/200 251 frequencyMap.put(new Sleep(), 0.125); // 25/200 252 frequencyMap.put(new TimedWait(lock), 0.05); // 10/200 253 frequencyMap.put(new Wait(lock), 0.075); // 15/200 254 255 return frequencyMap; 256 } 257 createLockFrequencyMap(Object lock)258 private final static Map<Operation, Double> createLockFrequencyMap(Object lock) { 259 Map<Operation, Double> frequencyMap = new HashMap<Operation, Double>(); 260 frequencyMap.put(new Sleep(), 0.2); 261 frequencyMap.put(new TimedWait(lock), 0.2); 262 frequencyMap.put(new Wait(lock), 0.2); 263 frequencyMap.put(new SyncAndWork(lock), 0.4); 264 265 return frequencyMap; 266 } 267 main(String[] args)268 public static void main(String[] args) throws Exception { 269 System.loadLibrary(args[0]); 270 parseAndRun(args); 271 } 272 updateFrequencyMap(Map<Operation, Double> in, Object lock, String arg)273 private static Map<Operation, Double> updateFrequencyMap(Map<Operation, Double> in, 274 Object lock, String arg) { 275 String split[] = arg.split(":"); 276 if (split.length != 2) { 277 throw new IllegalArgumentException("Can't split argument " + arg); 278 } 279 double d; 280 try { 281 d = Double.parseDouble(split[1]); 282 } catch (Exception e) { 283 throw new IllegalArgumentException(e); 284 } 285 if (d < 0) { 286 throw new IllegalArgumentException(arg + ": value must be >= 0."); 287 } 288 Operation op = null; 289 if (split[0].equals("-oom")) { 290 op = new OOM(); 291 } else if (split[0].equals("-sigquit")) { 292 op = new SigQuit(); 293 } else if (split[0].equals("-alloc")) { 294 op = new Alloc(); 295 } else if (split[0].equals("-largealloc")) { 296 op = new LargeAlloc(); 297 } else if (split[0].equals("-stacktrace")) { 298 op = new StackTrace(); 299 } else if (split[0].equals("-exit")) { 300 op = new Exit(); 301 } else if (split[0].equals("-sleep")) { 302 op = new Sleep(); 303 } else if (split[0].equals("-wait")) { 304 op = new Wait(lock); 305 } else if (split[0].equals("-timedwait")) { 306 op = new TimedWait(lock); 307 } else { 308 throw new IllegalArgumentException("Unknown arg " + arg); 309 } 310 311 if (in == null) { 312 in = new HashMap<Operation, Double>(); 313 } 314 in.put(op, d); 315 316 return in; 317 } 318 normalize(Map<Operation, Double> map)319 private static void normalize(Map<Operation, Double> map) { 320 double sum = 0; 321 for (Double d : map.values()) { 322 sum += d; 323 } 324 if (sum == 0) { 325 throw new RuntimeException("No elements!"); 326 } 327 if (sum != 1.0) { 328 // Avoid ConcurrentModificationException. 329 Set<Operation> tmp = new HashSet<>(map.keySet()); 330 for (Operation op : tmp) { 331 map.put(op, map.get(op) / sum); 332 } 333 } 334 } 335 parseAndRun(String[] args)336 public static void parseAndRun(String[] args) throws Exception { 337 int numberOfThreads = -1; 338 int numberOfDaemons = -1; 339 int totalOperations = -1; 340 int operationsPerThread = -1; 341 Object lock = new Object(); 342 Map<Operation, Double> frequencyMap = null; 343 boolean dumpMap = false; 344 345 if (args != null) { 346 // args[0] is libarttest 347 for (int i = 1; i < args.length; i++) { 348 if (args[i].equals("-n")) { 349 i++; 350 numberOfThreads = Integer.parseInt(args[i]); 351 } else if (args[i].equals("-d")) { 352 i++; 353 numberOfDaemons = Integer.parseInt(args[i]); 354 } else if (args[i].equals("-o")) { 355 i++; 356 totalOperations = Integer.parseInt(args[i]); 357 } else if (args[i].equals("-t")) { 358 i++; 359 operationsPerThread = Integer.parseInt(args[i]); 360 } else if (args[i].equals("--locks-only")) { 361 lock = new Object(); 362 frequencyMap = createLockFrequencyMap(lock); 363 } else if (args[i].equals("--dumpmap")) { 364 dumpMap = true; 365 } else { 366 frequencyMap = updateFrequencyMap(frequencyMap, lock, args[i]); 367 } 368 } 369 } 370 371 if (totalOperations != -1 && operationsPerThread != -1) { 372 throw new IllegalArgumentException( 373 "Specified both totalOperations and operationsPerThread"); 374 } 375 376 if (numberOfThreads == -1) { 377 numberOfThreads = 5; 378 } 379 380 if (numberOfDaemons == -1) { 381 numberOfDaemons = 3; 382 } 383 384 if (totalOperations == -1) { 385 totalOperations = 1000; 386 } 387 388 if (operationsPerThread == -1) { 389 operationsPerThread = totalOperations/numberOfThreads; 390 } 391 392 if (frequencyMap == null) { 393 frequencyMap = createDefaultFrequencyMap(lock); 394 } 395 normalize(frequencyMap); 396 397 if (dumpMap) { 398 System.out.println(frequencyMap); 399 } 400 401 try { 402 runTest(numberOfThreads, numberOfDaemons, operationsPerThread, lock, frequencyMap); 403 } catch (Throwable t) { 404 // In this case, the output should not contain all the required 405 // "Finishing worker" lines. 406 Main.printThrowable(t); 407 } 408 } 409 runTest(final int numberOfThreads, final int numberOfDaemons, final int operationsPerThread, final Object lock, Map<Operation, Double> frequencyMap)410 public static void runTest(final int numberOfThreads, final int numberOfDaemons, 411 final int operationsPerThread, final Object lock, 412 Map<Operation, Double> frequencyMap) throws Exception { 413 final Thread mainThread = Thread.currentThread(); 414 final Barrier startBarrier = new Barrier(numberOfThreads + numberOfDaemons + 1); 415 416 // Each normal thread is going to do operationsPerThread 417 // operations. Each daemon thread will loop over all 418 // the operations and will not stop. 419 // The distribution of operations is determined by 420 // the Operation.frequency values. We fill out an Operation[] 421 // for each thread with the operations it is to perform. The 422 // Operation[] is shuffled so that there is more random 423 // interactions between the threads. 424 425 // Fill in the Operation[] array for each thread by laying 426 // down references to operation according to their desired 427 // frequency. 428 // The first numberOfThreads elements are normal threads, the last 429 // numberOfDaemons elements are daemon threads. 430 final Main[] threadStresses = new Main[numberOfThreads + numberOfDaemons]; 431 for (int t = 0; t < threadStresses.length; t++) { 432 Operation[] operations = new Operation[operationsPerThread]; 433 int o = 0; 434 LOOP: 435 while (true) { 436 for (Operation op : frequencyMap.keySet()) { 437 int freq = (int)(frequencyMap.get(op) * operationsPerThread); 438 for (int f = 0; f < freq; f++) { 439 if (o == operations.length) { 440 break LOOP; 441 } 442 operations[o] = op; 443 o++; 444 } 445 } 446 } 447 // Randomize the operation order 448 Collections.shuffle(Arrays.asList(operations)); 449 threadStresses[t] = (t < numberOfThreads) 450 ? new Main(lock, t, operations) 451 : new Daemon(lock, t, operations, mainThread, startBarrier); 452 } 453 454 // Enable to dump operation counts per thread to make sure its 455 // sane compared to Operation.frequency 456 if (DEBUG) { 457 for (int t = 0; t < threadStresses.length; t++) { 458 Operation[] operations = threadStresses[t].operations; 459 Map<Operation, Integer> distribution = new HashMap<Operation, Integer>(); 460 for (Operation operation : operations) { 461 Integer ops = distribution.get(operation); 462 if (ops == null) { 463 ops = 1; 464 } else { 465 ops++; 466 } 467 distribution.put(operation, ops); 468 } 469 System.out.println("Distribution for " + t); 470 for (Operation op : frequencyMap.keySet()) { 471 System.out.println(op + " = " + distribution.get(op)); 472 } 473 } 474 } 475 476 // Create the runners for each thread. The runner Thread 477 // ensures that thread that exit due to Operation.EXIT will be 478 // restarted until they reach their desired 479 // operationsPerThread. 480 Thread[] runners = new Thread[numberOfThreads]; 481 for (int r = 0; r < runners.length; r++) { 482 final Main ts = threadStresses[r]; 483 runners[r] = new Thread("Runner thread " + r) { 484 final Main threadStress = ts; 485 public void run() { 486 try { 487 int id = threadStress.id; 488 // No memory hungry task are running yet, so println() should succeed. 489 System.out.println("Starting worker for " + id); 490 // Wait until all runners and daemons reach the starting point. 491 startBarrier.await(); 492 // Run the stress tasks. 493 while (threadStress.nextOperation < operationsPerThread) { 494 try { 495 Thread thread = new Thread(ts, "Worker thread " + id); 496 thread.start(); 497 thread.join(); 498 499 if (DEBUG) { 500 System.out.println( 501 "Thread exited for " + id + " with " + 502 (operationsPerThread - threadStress.nextOperation) + 503 " operations remaining."); 504 } 505 } catch (OutOfMemoryError e) { 506 // Ignore OOME since we need to print "Finishing worker" 507 // for the test to pass. This OOM can come from creating 508 // the Thread or from the DEBUG output. 509 // Note that the Thread creation may fail repeatedly, 510 // preventing the runner from making any progress, 511 // especially if the number of daemons is too high. 512 } 513 } 514 // Print "Finishing worker" through JNI to avoid OOME. 515 Main.printString(Main.finishingWorkerMessage); 516 } catch (Throwable t) { 517 Main.printThrowable(t); 518 // Interrupt the main thread, so that it can orderly shut down 519 // instead of waiting indefinitely for some Barrier. 520 mainThread.interrupt(); 521 } 522 } 523 }; 524 } 525 526 // The notifier thread is a daemon just loops forever to wake 527 // up threads in Operation.WAIT 528 if (lock != null) { 529 Thread notifier = new Thread("Notifier") { 530 public void run() { 531 while (true) { 532 synchronized (lock) { 533 lock.notifyAll(); 534 } 535 } 536 } 537 }; 538 notifier.setDaemon(true); 539 notifier.start(); 540 } 541 542 // Create and start the daemon threads. 543 for (int r = 0; r < numberOfDaemons; r++) { 544 Main daemon = threadStresses[numberOfThreads + r]; 545 Thread t = new Thread(daemon, "Daemon thread " + daemon.id); 546 t.setDaemon(true); 547 t.start(); 548 } 549 550 for (int r = 0; r < runners.length; r++) { 551 runners[r].start(); 552 } 553 // Wait for all threads to reach the starting point. 554 startBarrier.await(); 555 // Wait for runners to finish. 556 for (int r = 0; r < runners.length; r++) { 557 runners[r].join(); 558 } 559 } 560 561 protected final Operation[] operations; 562 private final Object lock; 563 protected final int id; 564 565 private int nextOperation; 566 Main(Object lock, int id, Operation[] operations)567 private Main(Object lock, int id, Operation[] operations) { 568 this.lock = lock; 569 this.id = id; 570 this.operations = operations; 571 } 572 run()573 public void run() { 574 try { 575 if (DEBUG) { 576 System.out.println("Starting ThreadStress " + id); 577 } 578 while (nextOperation < operations.length) { 579 Operation operation = operations[nextOperation]; 580 if (DEBUG) { 581 System.out.println("ThreadStress " + id 582 + " operation " + nextOperation 583 + " is " + operation); 584 } 585 nextOperation++; 586 if (!operation.perform()) { 587 return; 588 } 589 } 590 } finally { 591 if (DEBUG) { 592 System.out.println("Finishing ThreadStress for " + id); 593 } 594 } 595 } 596 597 private static class Daemon extends Main { Daemon(Object lock, int id, Operation[] operations, Thread mainThread, Barrier startBarrier)598 private Daemon(Object lock, 599 int id, 600 Operation[] operations, 601 Thread mainThread, 602 Barrier startBarrier) { 603 super(lock, id, operations); 604 this.mainThread = mainThread; 605 this.startBarrier = startBarrier; 606 } 607 run()608 public void run() { 609 try { 610 if (DEBUG) { 611 System.out.println("Starting ThreadStress Daemon " + id); 612 } 613 startBarrier.await(); 614 try { 615 int i = 0; 616 while (true) { 617 Operation operation = operations[i]; 618 if (DEBUG) { 619 System.out.println("ThreadStress Daemon " + id 620 + " operation " + i 621 + " is " + operation); 622 } 623 operation.perform(); 624 i = (i + 1) % operations.length; 625 } 626 } catch (OutOfMemoryError e) { 627 // Catch OutOfMemoryErrors since these can cause the test to fail it they print 628 // the stack trace after "Finishing worker". Note that operations should catch 629 // their own OOME, this guards only agains OOME in the DEBUG output. 630 } 631 if (DEBUG) { 632 System.out.println("Finishing ThreadStress Daemon for " + id); 633 } 634 } catch (Throwable t) { 635 Main.printThrowable(t); 636 // Interrupt the main thread, so that it can orderly shut down 637 // instead of waiting indefinitely for some Barrier. 638 mainThread.interrupt(); 639 } 640 } 641 642 final Thread mainThread; 643 final Barrier startBarrier; 644 } 645 646 // Note: java.util.concurrent.CyclicBarrier.await() allocates memory and may throw OOM. 647 // That is highly undesirable in this test, so we use our own simple barrier class. 648 // The only memory allocation that can happen here is the lock inflation which uses 649 // a native allocation. As such, it should succeed even if the Java heap is full. 650 // If the native allocation surprisingly fails, the program shall abort(). 651 private static class Barrier { Barrier(int initialCount)652 public Barrier(int initialCount) { 653 count = initialCount; 654 } 655 await()656 public synchronized void await() throws InterruptedException { 657 --count; 658 if (count != 0) { 659 do { 660 wait(); 661 } while (count != 0); // Check for spurious wakeup. 662 } else { 663 notifyAll(); 664 } 665 } 666 667 private int count; 668 } 669 670 // Printing a String/Throwable through JNI requires only native memory and space 671 // in the local reference table, so it should succeed even if the Java heap is full. printString(String s)672 private static native void printString(String s); printThrowable(Throwable t)673 private static native void printThrowable(Throwable t); 674 675 static final String finishingWorkerMessage; 676 static final String errnoExceptionName; 677 static { 678 // We pre-allocate the strings in class initializer to avoid const-string 679 // instructions in code using these strings later as they may throw OOME. 680 finishingWorkerMessage = "Finishing worker\n"; 681 errnoExceptionName = "ErrnoException"; 682 } 683 } 684