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//    -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
46public 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         */
55        public abstract boolean perform();
56    }
57
58    private final static class OOM extends Operation {
59        @Override
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
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
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
132        public boolean perform() {
133            Thread.currentThread().getStackTrace();
134            return true;
135        }
136    }
137
138    private final static class Exit extends Operation {
139        @Override
140        public boolean perform() {
141            return false;
142        }
143    }
144
145    private final static class Sleep extends Operation {
146        @Override
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
159        public TimedWait(Object lock) {
160            this.lock = lock;
161        }
162
163        @Override
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
178        public Wait(Object lock) {
179            this.lock = lock;
180        }
181
182        @Override
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
197        public SyncAndWork(Object lock) {
198            this.lock = lock;
199        }
200
201        @Override
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
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
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
237    public static void main(String[] args) throws Exception {
238        parseAndRun(args);
239    }
240
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
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
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
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
476    private Main(Object lock, int id, Operation[] operations) {
477        this.lock = lock;
478        this.id = id;
479        this.operations = operations;
480    }
481
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