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.HashSet; 23import java.util.List; 24import java.util.Map; 25import 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 47public 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 */ 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 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 if (!e.getClass().getName().equals("ErrnoException")) { 97 e.printStackTrace(System.out); 98 } 99 } 100 101 pid = pidTemp; 102 sigquit = sigquitTemp; 103 kill = killTemp; 104 } 105 106 @Override 107 public boolean perform() { 108 try { 109 kill.invoke(null, pid, sigquit); 110 } catch (Exception e) { 111 if (!e.getClass().getName().equals("ErrnoException")) { 112 e.printStackTrace(System.out); 113 } 114 } 115 return true; 116 } 117 } 118 119 private final static class Alloc extends Operation { 120 private final static int ALLOC_SIZE = 1024; // Needs to be small enough to not be in LOS. 121 private final static int ALLOC_COUNT = 1024; 122 123 @Override 124 public boolean perform() { 125 try { 126 List<byte[]> l = new ArrayList<byte[]>(); 127 for (int i = 0; i < ALLOC_COUNT; i++) { 128 l.add(new byte[ALLOC_SIZE]); 129 } 130 } catch (OutOfMemoryError e) { 131 } 132 return true; 133 } 134 } 135 136 private final static class LargeAlloc extends Operation { 137 private final static int PAGE_SIZE = 4096; 138 private final static int PAGE_SIZE_MODIFIER = 10; // Needs to be large enough for LOS. 139 private final static int ALLOC_COUNT = 100; 140 141 @Override 142 public boolean perform() { 143 try { 144 List<byte[]> l = new ArrayList<byte[]>(); 145 for (int i = 0; i < ALLOC_COUNT; i++) { 146 l.add(new byte[PAGE_SIZE_MODIFIER * PAGE_SIZE]); 147 } 148 } catch (OutOfMemoryError e) { 149 } 150 return true; 151 } 152 } 153 154 private final static class StackTrace extends Operation { 155 @Override 156 public boolean perform() { 157 Thread.currentThread().getStackTrace(); 158 return true; 159 } 160 } 161 162 private final static class Exit extends Operation { 163 @Override 164 public boolean perform() { 165 return false; 166 } 167 } 168 169 private final static class Sleep extends Operation { 170 private final static int SLEEP_TIME = 100; 171 172 @Override 173 public boolean perform() { 174 try { 175 Thread.sleep(SLEEP_TIME); 176 } catch (InterruptedException ignored) { 177 } 178 return true; 179 } 180 } 181 182 private final static class TimedWait extends Operation { 183 private final static int SLEEP_TIME = 100; 184 185 private final Object lock; 186 187 public TimedWait(Object lock) { 188 this.lock = lock; 189 } 190 191 @Override 192 public boolean perform() { 193 synchronized (lock) { 194 try { 195 lock.wait(SLEEP_TIME, 0); 196 } catch (InterruptedException ignored) { 197 } 198 } 199 return true; 200 } 201 } 202 203 private final static class Wait extends Operation { 204 private final Object lock; 205 206 public Wait(Object lock) { 207 this.lock = lock; 208 } 209 210 @Override 211 public boolean perform() { 212 synchronized (lock) { 213 try { 214 lock.wait(); 215 } catch (InterruptedException ignored) { 216 } 217 } 218 return true; 219 } 220 } 221 222 private final static class SyncAndWork extends Operation { 223 private final Object lock; 224 225 public SyncAndWork(Object lock) { 226 this.lock = lock; 227 } 228 229 @Override 230 public boolean perform() { 231 synchronized (lock) { 232 try { 233 Thread.sleep((int)(Math.random()*10)); 234 } catch (InterruptedException ignored) { 235 } 236 } 237 return true; 238 } 239 } 240 241 private final static Map<Operation, Double> createDefaultFrequencyMap(Object lock) { 242 Map<Operation, Double> frequencyMap = new HashMap<Operation, Double>(); 243 frequencyMap.put(new OOM(), 0.005); // 1/200 244 frequencyMap.put(new SigQuit(), 0.095); // 19/200 245 frequencyMap.put(new Alloc(), 0.25); // 50/200 246 frequencyMap.put(new LargeAlloc(), 0.05); // 10/200 247 frequencyMap.put(new StackTrace(), 0.1); // 20/200 248 frequencyMap.put(new Exit(), 0.25); // 50/200 249 frequencyMap.put(new Sleep(), 0.125); // 25/200 250 frequencyMap.put(new TimedWait(lock), 0.05); // 10/200 251 frequencyMap.put(new Wait(lock), 0.075); // 15/200 252 253 return frequencyMap; 254 } 255 256 private final static Map<Operation, Double> createLockFrequencyMap(Object lock) { 257 Map<Operation, Double> frequencyMap = new HashMap<Operation, Double>(); 258 frequencyMap.put(new Sleep(), 0.2); 259 frequencyMap.put(new TimedWait(lock), 0.2); 260 frequencyMap.put(new Wait(lock), 0.2); 261 frequencyMap.put(new SyncAndWork(lock), 0.4); 262 263 return frequencyMap; 264 } 265 266 public static void main(String[] args) throws Exception { 267 parseAndRun(args); 268 } 269 270 private static Map<Operation, Double> updateFrequencyMap(Map<Operation, Double> in, 271 Object lock, String arg) { 272 String split[] = arg.split(":"); 273 if (split.length != 2) { 274 throw new IllegalArgumentException("Can't split argument " + arg); 275 } 276 double d; 277 try { 278 d = Double.parseDouble(split[1]); 279 } catch (Exception e) { 280 throw new IllegalArgumentException(e); 281 } 282 if (d < 0) { 283 throw new IllegalArgumentException(arg + ": value must be >= 0."); 284 } 285 Operation op = null; 286 if (split[0].equals("-oom")) { 287 op = new OOM(); 288 } else if (split[0].equals("-sigquit")) { 289 op = new SigQuit(); 290 } else if (split[0].equals("-alloc")) { 291 op = new Alloc(); 292 } else if (split[0].equals("-largealloc")) { 293 op = new LargeAlloc(); 294 } else if (split[0].equals("-stacktrace")) { 295 op = new StackTrace(); 296 } else if (split[0].equals("-exit")) { 297 op = new Exit(); 298 } else if (split[0].equals("-sleep")) { 299 op = new Sleep(); 300 } else if (split[0].equals("-wait")) { 301 op = new Wait(lock); 302 } else if (split[0].equals("-timedwait")) { 303 op = new TimedWait(lock); 304 } else { 305 throw new IllegalArgumentException("Unknown arg " + arg); 306 } 307 308 if (in == null) { 309 in = new HashMap<Operation, Double>(); 310 } 311 in.put(op, d); 312 313 return in; 314 } 315 316 private static void normalize(Map<Operation, Double> map) { 317 double sum = 0; 318 for (Double d : map.values()) { 319 sum += d; 320 } 321 if (sum == 0) { 322 throw new RuntimeException("No elements!"); 323 } 324 if (sum != 1.0) { 325 // Avoid ConcurrentModificationException. 326 Set<Operation> tmp = new HashSet<>(map.keySet()); 327 for (Operation op : tmp) { 328 map.put(op, map.get(op) / sum); 329 } 330 } 331 } 332 333 public static void parseAndRun(String[] args) throws Exception { 334 int numberOfThreads = -1; 335 int numberOfDaemons = -1; 336 int totalOperations = -1; 337 int operationsPerThread = -1; 338 Object lock = new Object(); 339 Map<Operation, Double> frequencyMap = null; 340 boolean dumpMap = false; 341 342 if (args != null) { 343 // args[0] is libarttest 344 for (int i = 1; i < args.length; i++) { 345 if (args[i].equals("-n")) { 346 i++; 347 numberOfThreads = Integer.parseInt(args[i]); 348 } else if (args[i].equals("-d")) { 349 i++; 350 numberOfDaemons = Integer.parseInt(args[i]); 351 } else if (args[i].equals("-o")) { 352 i++; 353 totalOperations = Integer.parseInt(args[i]); 354 } else if (args[i].equals("-t")) { 355 i++; 356 operationsPerThread = Integer.parseInt(args[i]); 357 } else if (args[i].equals("--locks-only")) { 358 lock = new Object(); 359 frequencyMap = createLockFrequencyMap(lock); 360 } else if (args[i].equals("--dumpmap")) { 361 dumpMap = true; 362 } else { 363 frequencyMap = updateFrequencyMap(frequencyMap, lock, args[i]); 364 } 365 } 366 } 367 368 if (totalOperations != -1 && operationsPerThread != -1) { 369 throw new IllegalArgumentException( 370 "Specified both totalOperations and operationsPerThread"); 371 } 372 373 if (numberOfThreads == -1) { 374 numberOfThreads = 5; 375 } 376 377 if (numberOfDaemons == -1) { 378 numberOfDaemons = 3; 379 } 380 381 if (totalOperations == -1) { 382 totalOperations = 1000; 383 } 384 385 if (operationsPerThread == -1) { 386 operationsPerThread = totalOperations/numberOfThreads; 387 } 388 389 if (frequencyMap == null) { 390 frequencyMap = createDefaultFrequencyMap(lock); 391 } 392 normalize(frequencyMap); 393 394 if (dumpMap) { 395 System.out.println(frequencyMap); 396 } 397 398 runTest(numberOfThreads, numberOfDaemons, operationsPerThread, lock, frequencyMap); 399 } 400 401 public static void runTest(final int numberOfThreads, final int numberOfDaemons, 402 final int operationsPerThread, final Object lock, 403 Map<Operation, Double> frequencyMap) throws Exception { 404 // Each normal thread is going to do operationsPerThread 405 // operations. Each daemon thread will loop over all 406 // the operations and will not stop. 407 // The distribution of operations is determined by 408 // the Operation.frequency values. We fill out an Operation[] 409 // for each thread with the operations it is to perform. The 410 // Operation[] is shuffled so that there is more random 411 // interactions between the threads. 412 413 // Fill in the Operation[] array for each thread by laying 414 // down references to operation according to their desired 415 // frequency. 416 // The first numberOfThreads elements are normal threads, the last 417 // numberOfDaemons elements are daemon threads. 418 final Main[] threadStresses = new Main[numberOfThreads + numberOfDaemons]; 419 for (int t = 0; t < threadStresses.length; t++) { 420 Operation[] operations = new Operation[operationsPerThread]; 421 int o = 0; 422 LOOP: 423 while (true) { 424 for (Operation op : frequencyMap.keySet()) { 425 int freq = (int)(frequencyMap.get(op) * operationsPerThread); 426 for (int f = 0; f < freq; f++) { 427 if (o == operations.length) { 428 break LOOP; 429 } 430 operations[o] = op; 431 o++; 432 } 433 } 434 } 435 // Randomize the operation order 436 Collections.shuffle(Arrays.asList(operations)); 437 threadStresses[t] = t < numberOfThreads ? new Main(lock, t, operations) : 438 new Daemon(lock, t, operations); 439 } 440 441 // Enable to dump operation counts per thread to make sure its 442 // sane compared to Operation.frequency 443 if (DEBUG) { 444 for (int t = 0; t < threadStresses.length; t++) { 445 Operation[] operations = threadStresses[t].operations; 446 Map<Operation, Integer> distribution = new HashMap<Operation, Integer>(); 447 for (Operation operation : operations) { 448 Integer ops = distribution.get(operation); 449 if (ops == null) { 450 ops = 1; 451 } else { 452 ops++; 453 } 454 distribution.put(operation, ops); 455 } 456 System.out.println("Distribution for " + t); 457 for (Operation op : frequencyMap.keySet()) { 458 System.out.println(op + " = " + distribution.get(op)); 459 } 460 } 461 } 462 463 // Create the runners for each thread. The runner Thread 464 // ensures that thread that exit due to Operation.EXIT will be 465 // restarted until they reach their desired 466 // operationsPerThread. 467 Thread[] runners = new Thread[numberOfThreads]; 468 for (int r = 0; r < runners.length; r++) { 469 final Main ts = threadStresses[r]; 470 runners[r] = new Thread("Runner thread " + r) { 471 final Main threadStress = ts; 472 public void run() { 473 int id = threadStress.id; 474 System.out.println("Starting worker for " + id); 475 while (threadStress.nextOperation < operationsPerThread) { 476 try { 477 Thread thread = new Thread(ts, "Worker thread " + id); 478 thread.start(); 479 try { 480 thread.join(); 481 } catch (InterruptedException e) { 482 } 483 484 System.out.println("Thread exited for " + id + " with " 485 + (operationsPerThread - threadStress.nextOperation) 486 + " operations remaining."); 487 } catch (OutOfMemoryError e) { 488 // Ignore OOME since we need to print "Finishing worker" for the test 489 // to pass. 490 } 491 } 492 // Keep trying to print "Finishing worker" until it succeeds. 493 while (true) { 494 try { 495 System.out.println("Finishing worker"); 496 break; 497 } catch (OutOfMemoryError e) { 498 } 499 } 500 } 501 }; 502 } 503 504 // The notifier thread is a daemon just loops forever to wake 505 // up threads in Operation.WAIT 506 if (lock != null) { 507 Thread notifier = new Thread("Notifier") { 508 public void run() { 509 while (true) { 510 synchronized (lock) { 511 lock.notifyAll(); 512 } 513 } 514 } 515 }; 516 notifier.setDaemon(true); 517 notifier.start(); 518 } 519 520 // Create and start the daemon threads. 521 for (int r = 0; r < numberOfDaemons; r++) { 522 Main daemon = threadStresses[numberOfThreads + r]; 523 Thread t = new Thread(daemon, "Daemon thread " + daemon.id); 524 t.setDaemon(true); 525 t.start(); 526 } 527 528 for (int r = 0; r < runners.length; r++) { 529 runners[r].start(); 530 } 531 for (int r = 0; r < runners.length; r++) { 532 runners[r].join(); 533 } 534 } 535 536 protected final Operation[] operations; 537 private final Object lock; 538 protected final int id; 539 540 private int nextOperation; 541 542 private Main(Object lock, int id, Operation[] operations) { 543 this.lock = lock; 544 this.id = id; 545 this.operations = operations; 546 } 547 548 public void run() { 549 try { 550 if (DEBUG) { 551 System.out.println("Starting ThreadStress " + id); 552 } 553 while (nextOperation < operations.length) { 554 Operation operation = operations[nextOperation]; 555 if (DEBUG) { 556 System.out.println("ThreadStress " + id 557 + " operation " + nextOperation 558 + " is " + operation); 559 } 560 nextOperation++; 561 if (!operation.perform()) { 562 return; 563 } 564 } 565 } finally { 566 if (DEBUG) { 567 System.out.println("Finishing ThreadStress for " + id); 568 } 569 } 570 } 571 572 private static class Daemon extends Main { 573 private Daemon(Object lock, int id, Operation[] operations) { 574 super(lock, id, operations); 575 } 576 577 public void run() { 578 try { 579 if (DEBUG) { 580 System.out.println("Starting ThreadStress Daemon " + id); 581 } 582 int i = 0; 583 while (true) { 584 Operation operation = operations[i]; 585 if (DEBUG) { 586 System.out.println("ThreadStress Daemon " + id 587 + " operation " + i 588 + " is " + operation); 589 } 590 operation.perform(); 591 i = (i + 1) % operations.length; 592 } 593 } catch (OutOfMemoryError e) { 594 // Catch OutOfMemoryErrors since these can cause the test to fail it they print 595 // the stack trace after "Finishing worker". 596 } finally { 597 if (DEBUG) { 598 System.out.println("Finishing ThreadStress Daemon for " + id); 599 } 600 } 601 } 602 } 603 604} 605