blob: c294a3861a8e36e55d926b82ce63f68d2ed0f1fd [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
// Run on host with:
// javac ThreadTest.java && java ThreadStress && rm *.class
class ThreadStress implements Runnable {
public static final boolean DEBUG = false;
enum Operation {
OOM(1),
ALLOC(99),
EXIT(50),
WAIT(50);
private final int frequency;
Operation(int frequency) {
this.frequency = frequency;
}
}
public static void main(String[] args) throws Exception {
final int numberOfThreads = 5;
final int totalOperations = 1000;
final int operationsPerThread = totalOperations/numberOfThreads;
// Lock used to notify threads performin Operation.WAIT
final Object lock = new Object();
// Each thread is going to do operationsPerThread
// operations. The distribution of operations is determined by
// the Operation.frequency values. We fill out an Operation[]
// for each thread with the operations it is to perform. The
// Operation[] is shuffled so that there is more random
// interactions between the threads.
// The simple-minded filling in of Operation[] based on
// Operation.frequency below won't have even have close to a
// reasonable distribution if the count of Operation
// frequencies is greater than the total number of
// operations. So here we do a quick sanity check in case
// people tweak the constants above.
int operationCount = 0;
for (Operation op : Operation.values()) {
operationCount += op.frequency;
}
if (operationCount > operationsPerThread) {
throw new AssertionError(operationCount + " > " + operationsPerThread);
}
// Fill in the Operation[] array for each thread by laying
// down references to operation according to their desired
// frequency.
final ThreadStress[] threadStresses = new ThreadStress[numberOfThreads];
for (int t = 0; t < threadStresses.length; t++) {
Operation[] operations = new Operation[operationsPerThread];
int o = 0;
LOOP:
while (true) {
for (Operation op : Operation.values()) {
for (int f = 0; f < op.frequency; f++) {
if (o == operations.length) {
break LOOP;
}
operations[o] = op;
o++;
}
}
}
// Randomize the oepration order
Collections.shuffle(Arrays.asList(operations));
threadStresses[t] = new ThreadStress(lock, t, operations);
}
// Enable to dump operation counds per thread to make sure its
// sane compared to Operation.frequency
if (DEBUG) {
for (int t = 0; t < threadStresses.length; t++) {
Operation[] operations = new Operation[operationsPerThread];
Map<Operation, Integer> distribution = new HashMap<Operation, Integer>();
for (Operation operation : operations) {
Integer ops = distribution.get(operation);
if (ops == null) {
ops = 1;
} else {
ops++;
}
distribution.put(operation, ops);
}
System.out.println("Distribution for " + t);
for (Operation op : Operation.values()) {
System.out.println(op + " = " + distribution.get(op));
}
}
}
// Create the runners for each thread. The runner Thread
// ensures that thread that exit due to Operation.EXIT will be
// restarted until they reach their desired
// operationsPerThread.
Thread[] runners = new Thread[numberOfThreads];
for (int r = 0; r < runners.length; r++) {
final ThreadStress ts = threadStresses[r];
runners[r] = new Thread() {
final ThreadStress threadStress = ts;
public void run() {
int id = threadStress.id;
System.out.println("Starting runner for " + id);
while (threadStress.nextOperation < operationsPerThread) {
Thread thread = new Thread(ts);
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
}
System.out.println("Thread exited for " + id + " with "
+ (operationsPerThread - threadStress.nextOperation)
+ " operations remaining.");
}
System.out.println("Finishing runner for " + id);
}
};
}
// The notifier thread is a daemon just loops forever to wake
// up threads in Operation.WAIT
Thread notifier = new Thread() {
public void run() {
while (true) {
synchronized (lock) {
lock.notifyAll();
}
}
}
};
notifier.setDaemon(true);
notifier.start();
for (int r = 0; r < runners.length; r++) {
runners[r].start();
}
for (int r = 0; r < runners.length; r++) {
runners[r].join();
}
}
private final Operation[] operations;
private final Object lock;
private final int id;
private int nextOperation;
private ThreadStress(Object lock, int id, Operation[] operations) {
this.lock = lock;
this.id = id;
this.operations = operations;
}
public void run() {
try {
if (DEBUG) {
System.out.println("Starting ThreadStress " + id);
}
while (nextOperation < operations.length) {
Operation operation = operations[nextOperation];
if (DEBUG) {
System.out.println("ThreadStress " + id
+ " operation " + nextOperation
+ " is " + operation);
}
nextOperation++;
switch (operation) {
case EXIT: {
return;
}
case WAIT: {
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
}
}
break;
}
case OOM: {
try {
List<byte[]> l = new ArrayList<byte[]>();
while (true) {
l.add(new byte[1024]);
}
} catch (OutOfMemoryError e) {
}
break;
}
case ALLOC: {
try {
List<byte[]> l = new ArrayList<byte[]>();
for (int i = 0; i < 1024; i++) {
l.add(new byte[1024]);
}
} catch (OutOfMemoryError e) {
}
break;
}
default: {
throw new AssertionError(operation.toString());
}
}
}
} finally {
if (DEBUG) {
System.out.println("Finishing ThreadStress for " + id);
}
}
}
}