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