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 // -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 46 public class Main implements Runnable { 47 48 public static final boolean DEBUG = false; 49 50 private static abstract class Operation { 51 /** 52 * Perform the action represented by this operation. Returns true if the thread should 53 * continue. 54 */ perform()55 public abstract boolean perform(); 56 } 57 58 private final static class OOM extends Operation { 59 @Override perform()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; 69 } 70 } 71 72 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 perform()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 perform()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 perform()132 public boolean perform() { 133 Thread.currentThread().getStackTrace(); 134 return true; 135 } 136 } 137 138 private final static class Exit extends Operation { 139 @Override perform()140 public boolean perform() { 141 return false; 142 } 143 } 144 145 private final static class Sleep extends Operation { 146 @Override perform()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 TimedWait(Object lock)159 public TimedWait(Object lock) { 160 this.lock = lock; 161 } 162 163 @Override perform()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 Wait(Object lock)178 public Wait(Object lock) { 179 this.lock = lock; 180 } 181 182 @Override perform()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 SyncAndWork(Object lock)197 public SyncAndWork(Object lock) { 198 this.lock = lock; 199 } 200 201 @Override perform()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 createDefaultFrequencyMap(Object lock)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 createLockFrequencyMap(Object lock)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 main(String[] args)237 public static void main(String[] args) throws Exception { 238 parseAndRun(args); 239 } 240 updateFrequencyMap(Map<Operation, Double> in, Object lock, String arg)241 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 } 276 277 if (in == null) { 278 in = new HashMap<Operation, Double>(); 279 } 280 in.put(op, d); 281 282 return in; 283 } 284 normalize(Map<Operation, Double> map)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 parseAndRun(String[] args)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 runTest(final int numberOfThreads, final int operationsPerThread, final Object lock, Map<Operation, Double> frequencyMap)361 public static void runTest(final int numberOfThreads, final int operationsPerThread, 362 final Object lock, Map<Operation, Double> frequencyMap) 363 throws Exception { 364 // 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 371 // Fill in the Operation[] array for each thread by laying 372 // down references to operation according to their desired 373 // frequency. 374 final Main[] threadStresses = new Main[numberOfThreads]; 375 for (int t = 0; t < threadStresses.length; t++) { 376 Operation[] operations = new Operation[operationsPerThread]; 377 int o = 0; 378 LOOP: 379 while (true) { 380 for (Operation op : frequencyMap.keySet()) { 381 int freq = (int)(frequencyMap.get(op) * operationsPerThread); 382 for (int f = 0; f < freq; f++) { 383 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)); 393 threadStresses[t] = new Main(lock, t, operations); 394 } 395 396 // Enable to dump operation counts per thread to make sure its 397 // sane compared to Operation.frequency 398 if (DEBUG) { 399 for (int t = 0; t < threadStresses.length; t++) { 400 Operation[] operations = threadStresses[t].operations; 401 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); 412 for (Operation op : frequencyMap.keySet()) { 413 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++) { 424 final Main ts = threadStresses[r]; 425 runners[r] = new Thread("Runner thread " + r) { 426 final Main threadStress = ts; 427 public void run() { 428 int id = threadStress.id; 429 System.out.println("Starting worker for " + id); 430 while (threadStress.nextOperation < operationsPerThread) { 431 Thread thread = new Thread(ts, "Worker thread " + id); 432 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 } 441 System.out.println("Finishing worker"); 442 } 443 }; 444 } 445 446 // The notifier thread is a daemon just loops forever to wake 447 // up threads in Operation.WAIT 448 if (lock != null) { 449 Thread notifier = new Thread("Notifier") { 450 public void run() { 451 while (true) { 452 synchronized (lock) { 453 lock.notifyAll(); 454 } 455 } 456 } 457 }; 458 notifier.setDaemon(true); 459 notifier.start(); 460 } 461 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 Main(Object lock, int id, Operation[] operations)476 private Main(Object lock, int id, Operation[] operations) { 477 this.lock = lock; 478 this.id = id; 479 this.operations = operations; 480 } 481 run()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++; 495 if (!operation.perform()) { 496 return; 497 } 498 } 499 } finally { 500 if (DEBUG) { 501 System.out.println("Finishing ThreadStress for " + id); 502 } 503 } 504 } 505 506 } 507