• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2007, 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 package com.android.commands.monkey;
18 
19 import android.app.ActivityManagerNative;
20 import android.app.IActivityController;
21 import android.app.IActivityManager;
22 import android.content.ComponentName;
23 import android.content.Intent;
24 import android.content.pm.IPackageManager;
25 import android.content.pm.ResolveInfo;
26 import android.os.Build;
27 import android.os.Debug;
28 import android.os.Environment;
29 import android.os.Process;
30 import android.os.RemoteException;
31 import android.os.ServiceManager;
32 import android.os.StrictMode;
33 import android.os.SystemClock;
34 import android.os.SystemProperties;
35 import android.os.UserHandle;
36 import android.view.IWindowManager;
37 import android.view.Surface;
38 
39 import java.io.BufferedReader;
40 import java.io.BufferedWriter;
41 import java.io.ByteArrayInputStream;
42 import java.io.DataInputStream;
43 import java.io.File;
44 import java.io.FileReader;
45 import java.io.FileWriter;
46 import java.io.IOException;
47 import java.io.InputStream;
48 import java.io.InputStreamReader;
49 import java.io.Writer;
50 import java.security.SecureRandom;
51 import java.util.ArrayList;
52 import java.util.HashSet;
53 import java.util.Iterator;
54 import java.util.List;
55 import java.util.Random;
56 
57 /**
58  * Application that injects random key events and other actions into the system.
59  */
60 public class Monkey {
61 
62     /**
63      * Monkey Debugging/Dev Support
64      * <p>
65      * All values should be zero when checking in.
66      */
67     private final static int DEBUG_ALLOW_ANY_STARTS = 0;
68 
69     private final static int DEBUG_ALLOW_ANY_RESTARTS = 0;
70 
71     private IActivityManager mAm;
72 
73     private IWindowManager mWm;
74 
75     private IPackageManager mPm;
76 
77     /** Command line arguments */
78     private String[] mArgs;
79 
80     /** Current argument being parsed */
81     private int mNextArg;
82 
83     /** Data of current argument */
84     private String mCurArgData;
85 
86     /** Running in verbose output mode? 1= verbose, 2=very verbose */
87     private int mVerbose;
88 
89     /** Ignore any application crashes while running? */
90     private boolean mIgnoreCrashes;
91 
92     /** Ignore any not responding timeouts while running? */
93     private boolean mIgnoreTimeouts;
94 
95     /** Ignore security exceptions when launching activities */
96     /** (The activity launch still fails, but we keep pluggin' away) */
97     private boolean mIgnoreSecurityExceptions;
98 
99     /** Monitor /data/tombstones and stop the monkey if new files appear. */
100     private boolean mMonitorNativeCrashes;
101 
102     /** Ignore any native crashes while running? */
103     private boolean mIgnoreNativeCrashes;
104 
105     /** Send no events. Use with long throttle-time to watch user operations */
106     private boolean mSendNoEvents;
107 
108     /** This is set when we would like to abort the running of the monkey. */
109     private boolean mAbort;
110 
111     /**
112      * Count each event as a cycle. Set to false for scripts so that each time
113      * through the script increments the count.
114      */
115     private boolean mCountEvents = true;
116 
117     /**
118      * This is set by the ActivityController thread to request collection of ANR
119      * trace files
120      */
121     private boolean mRequestAnrTraces = false;
122 
123     /**
124      * This is set by the ActivityController thread to request a
125      * "dumpsys meminfo"
126      */
127     private boolean mRequestDumpsysMemInfo = false;
128 
129     /**
130      * This is set by the ActivityController thread to request a
131      * bugreport after ANR
132      */
133     private boolean mRequestAnrBugreport = false;
134 
135     /**
136      * This is set by the ActivityController thread to request a
137      * bugreport after a system watchdog report
138      */
139     private boolean mRequestWatchdogBugreport = false;
140 
141     /**
142      * Synchronization for the ActivityController callback to block
143      * until we are done handling the reporting of the watchdog error.
144      */
145     private boolean mWatchdogWaiting = false;
146 
147     /**
148      * This is set by the ActivityController thread to request a
149      * bugreport after java application crash
150      */
151     private boolean mRequestAppCrashBugreport = false;
152 
153     /**Request the bugreport based on the mBugreportFrequency. */
154     private boolean mGetPeriodicBugreport = false;
155 
156     /**
157      * Request the bugreport based on the mBugreportFrequency.
158      */
159     private boolean mRequestPeriodicBugreport = false;
160 
161     /** Bugreport frequency. */
162     private long mBugreportFrequency = 10;
163 
164     /** Failure process name */
165     private String mReportProcessName;
166 
167     /**
168      * This is set by the ActivityController thread to request a "procrank"
169      */
170     private boolean mRequestProcRank = false;
171 
172     /** Kill the process after a timeout or crash. */
173     private boolean mKillProcessAfterError;
174 
175     /** Generate hprof reports before/after monkey runs */
176     private boolean mGenerateHprof;
177 
178     /** Package blacklist file. */
179     private String mPkgBlacklistFile;
180 
181     /** Package whitelist file. */
182     private String mPkgWhitelistFile;
183 
184     /** Packages we are allowed to run, or empty if no restriction. */
185     private HashSet<String> mValidPackages = new HashSet<String>();
186 
187     /** Packages we are not allowed to run. */
188     private HashSet<String> mInvalidPackages = new HashSet<String>();
189 
190     /** Categories we are allowed to launch **/
191     private ArrayList<String> mMainCategories = new ArrayList<String>();
192 
193     /** Applications we can switch to. */
194     private ArrayList<ComponentName> mMainApps = new ArrayList<ComponentName>();
195 
196     /** The delay between event inputs **/
197     long mThrottle = 0;
198 
199     /** Whether to randomize each throttle (0-mThrottle ms) inserted between events. */
200     boolean mRandomizeThrottle = false;
201 
202     /** The number of iterations **/
203     int mCount = 1000;
204 
205     /** The random number seed **/
206     long mSeed = 0;
207 
208     /** The random number generator **/
209     Random mRandom = null;
210 
211     /** Dropped-event statistics **/
212     long mDroppedKeyEvents = 0;
213 
214     long mDroppedPointerEvents = 0;
215 
216     long mDroppedTrackballEvents = 0;
217 
218     long mDroppedFlipEvents = 0;
219 
220     long mDroppedRotationEvents = 0;
221 
222     /** The delay between user actions. This is for the scripted monkey. **/
223     long mProfileWaitTime = 5000;
224 
225     /** Device idle time. This is for the scripted monkey. **/
226     long mDeviceSleepTime = 30000;
227 
228     boolean mRandomizeScript = false;
229 
230     boolean mScriptLog = false;
231 
232     /** Capture bugreprot whenever there is a crash. **/
233     private boolean mRequestBugreport = false;
234 
235     /** a filename to the setup script (if any) */
236     private String mSetupFileName = null;
237 
238     /** filenames of the script (if any) */
239     private ArrayList<String> mScriptFileNames = new ArrayList<String>();
240 
241     /** a TCP port to listen on for remote commands. */
242     private int mServerPort = -1;
243 
244     private static final File TOMBSTONES_PATH = new File("/data/tombstones");
245 
246     private HashSet<String> mTombstones = null;
247 
248     float[] mFactors = new float[MonkeySourceRandom.FACTORZ_COUNT];
249 
250     MonkeyEventSource mEventSource;
251 
252     private MonkeyNetworkMonitor mNetworkMonitor = new MonkeyNetworkMonitor();
253 
254     // information on the current activity.
255     public static Intent currentIntent;
256 
257     public static String currentPackage;
258 
259     /**
260      * Check whether we should run against the givn package.
261      *
262      * @param pkg The package name.
263      * @return Returns true if we should run against pkg.
264      */
checkEnteringPackage(String pkg)265     private boolean checkEnteringPackage(String pkg) {
266         if (mInvalidPackages.size() > 0) {
267             if (mInvalidPackages.contains(pkg)) {
268                 return false;
269             }
270         } else if (mValidPackages.size() > 0) {
271             if (!mValidPackages.contains(pkg)) {
272                 return false;
273             }
274         }
275         return true;
276     }
277 
278     /**
279      * Monitor operations happening in the system.
280      */
281     private class ActivityController extends IActivityController.Stub {
activityStarting(Intent intent, String pkg)282         public boolean activityStarting(Intent intent, String pkg) {
283             boolean allow = checkEnteringPackage(pkg) || (DEBUG_ALLOW_ANY_STARTS != 0);
284             if (mVerbose > 0) {
285                 // StrictMode's disk checks end up catching this on
286                 // userdebug/eng builds due to PrintStream going to a
287                 // FileOutputStream in the end (perhaps only when
288                 // redirected to a file?)  So we allow disk writes
289                 // around this region for the monkey to minimize
290                 // harmless dropbox uploads from monkeys.
291                 StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
292                 System.out.println("    // " + (allow ? "Allowing" : "Rejecting") + " start of "
293                         + intent + " in package " + pkg);
294                 StrictMode.setThreadPolicy(savedPolicy);
295             }
296             currentPackage = pkg;
297             currentIntent = intent;
298             return allow;
299         }
300 
activityResuming(String pkg)301         public boolean activityResuming(String pkg) {
302             StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
303             System.out.println("    // activityResuming(" + pkg + ")");
304             boolean allow = checkEnteringPackage(pkg) || (DEBUG_ALLOW_ANY_RESTARTS != 0);
305             if (!allow) {
306                 if (mVerbose > 0) {
307                     System.out.println("    // " + (allow ? "Allowing" : "Rejecting")
308                             + " resume of package " + pkg);
309                 }
310             }
311             currentPackage = pkg;
312             StrictMode.setThreadPolicy(savedPolicy);
313             return allow;
314         }
315 
appCrashed(String processName, int pid, String shortMsg, String longMsg, long timeMillis, String stackTrace)316         public boolean appCrashed(String processName, int pid,
317                 String shortMsg, String longMsg,
318                 long timeMillis, String stackTrace) {
319             StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
320             System.err.println("// CRASH: " + processName + " (pid " + pid + ")");
321             System.err.println("// Short Msg: " + shortMsg);
322             System.err.println("// Long Msg: " + longMsg);
323             System.err.println("// Build Label: " + Build.FINGERPRINT);
324             System.err.println("// Build Changelist: " + Build.VERSION.INCREMENTAL);
325             System.err.println("// Build Time: " + Build.TIME);
326             System.err.println("// " + stackTrace.replace("\n", "\n// "));
327             StrictMode.setThreadPolicy(savedPolicy);
328 
329             if (!mIgnoreCrashes || mRequestBugreport) {
330                 synchronized (Monkey.this) {
331                     if (!mIgnoreCrashes) {
332                         mAbort = true;
333                     }
334                     if (mRequestBugreport){
335                         mRequestAppCrashBugreport = true;
336                         mReportProcessName = processName;
337                     }
338                 }
339                 return !mKillProcessAfterError;
340             }
341             return false;
342         }
343 
appEarlyNotResponding(String processName, int pid, String annotation)344         public int appEarlyNotResponding(String processName, int pid, String annotation) {
345             return 0;
346         }
347 
appNotResponding(String processName, int pid, String processStats)348         public int appNotResponding(String processName, int pid, String processStats) {
349             StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
350             System.err.println("// NOT RESPONDING: " + processName + " (pid " + pid + ")");
351             System.err.println(processStats);
352             StrictMode.setThreadPolicy(savedPolicy);
353 
354             synchronized (Monkey.this) {
355                 mRequestAnrTraces = true;
356                 mRequestDumpsysMemInfo = true;
357                 mRequestProcRank = true;
358                 if (mRequestBugreport){
359                   mRequestAnrBugreport = true;
360                   mReportProcessName = processName;
361                 }
362             }
363             if (!mIgnoreTimeouts) {
364                 synchronized (Monkey.this) {
365                     mAbort = true;
366                 }
367             }
368             return (mKillProcessAfterError) ? -1 : 1;
369         }
370 
systemNotResponding(String message)371         public int systemNotResponding(String message) {
372             StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
373             System.err.println("// WATCHDOG: " + message);
374             StrictMode.setThreadPolicy(savedPolicy);
375 
376             synchronized (Monkey.this) {
377                 if (!mIgnoreCrashes) {
378                     mAbort = true;
379                 }
380                 if (mRequestBugreport) {
381                     mRequestWatchdogBugreport = true;
382                 }
383                 mWatchdogWaiting = true;
384             }
385             synchronized (Monkey.this) {
386                 while (mWatchdogWaiting) {
387                     try {
388                         Monkey.this.wait();
389                     } catch (InterruptedException e) {
390                     }
391                 }
392             }
393             return (mKillProcessAfterError) ? -1 : 1;
394         }
395     }
396 
397     /**
398      * Run the procrank tool to insert system status information into the debug
399      * report.
400      */
reportProcRank()401     private void reportProcRank() {
402         commandLineReport("procrank", "procrank");
403     }
404 
405     /**
406      * Run "cat /data/anr/traces.txt". Wait about 5 seconds first, to let the
407      * asynchronous report writing complete.
408      */
reportAnrTraces()409     private void reportAnrTraces() {
410         try {
411             Thread.sleep(5 * 1000);
412         } catch (InterruptedException e) {
413         }
414         commandLineReport("anr traces", "cat /data/anr/traces.txt");
415     }
416 
417     /**
418      * Run "dumpsys meminfo"
419      * <p>
420      * NOTE: You cannot perform a dumpsys call from the ActivityController
421      * callback, as it will deadlock. This should only be called from the main
422      * loop of the monkey.
423      */
reportDumpsysMemInfo()424     private void reportDumpsysMemInfo() {
425         commandLineReport("meminfo", "dumpsys meminfo");
426     }
427 
428     /**
429      * Print report from a single command line.
430      * <p>
431      * TODO: Use ProcessBuilder & redirectErrorStream(true) to capture both
432      * streams (might be important for some command lines)
433      *
434      * @param reportName Simple tag that will print before the report and in
435      *            various annotations.
436      * @param command Command line to execute.
437      */
commandLineReport(String reportName, String command)438     private void commandLineReport(String reportName, String command) {
439         System.err.println(reportName + ":");
440         Runtime rt = Runtime.getRuntime();
441         Writer logOutput = null;
442 
443         try {
444             // Process must be fully qualified here because android.os.Process
445             // is used elsewhere
446             java.lang.Process p = Runtime.getRuntime().exec(command);
447 
448             if (mRequestBugreport) {
449                 logOutput =
450                         new BufferedWriter(new FileWriter(new File(Environment
451                                 .getLegacyExternalStorageDirectory(), reportName), true));
452             }
453             // pipe everything from process stdout -> System.err
454             InputStream inStream = p.getInputStream();
455             InputStreamReader inReader = new InputStreamReader(inStream);
456             BufferedReader inBuffer = new BufferedReader(inReader);
457             String s;
458             while ((s = inBuffer.readLine()) != null) {
459                 if (mRequestBugreport) {
460                     logOutput.write(s);
461                     logOutput.write("\n");
462                 } else {
463                     System.err.println(s);
464                 }
465             }
466 
467             int status = p.waitFor();
468             System.err.println("// " + reportName + " status was " + status);
469 
470             if (logOutput != null) {
471                 logOutput.close();
472             }
473         } catch (Exception e) {
474             System.err.println("// Exception from " + reportName + ":");
475             System.err.println(e.toString());
476         }
477     }
478 
479     // Write the numbe of iteration to the log
writeScriptLog(int count)480     private void writeScriptLog(int count) {
481         // TO DO: Add the script file name to the log.
482         try {
483             Writer output = new BufferedWriter(new FileWriter(new File(
484                     Environment.getLegacyExternalStorageDirectory(), "scriptlog.txt"), true));
485             output.write("iteration: " + count + " time: "
486                     + MonkeyUtils.toCalendarTime(System.currentTimeMillis()) + "\n");
487             output.close();
488         } catch (IOException e) {
489             System.err.println(e.toString());
490         }
491     }
492 
493     // Write the bugreport to the sdcard.
getBugreport(String reportName)494     private void getBugreport(String reportName) {
495         reportName += MonkeyUtils.toCalendarTime(System.currentTimeMillis());
496         String bugreportName = reportName.replaceAll("[ ,:]", "_");
497         commandLineReport(bugreportName + ".txt", "bugreport");
498     }
499 
500     /**
501      * Command-line entry point.
502      *
503      * @param args The command-line arguments
504      */
main(String[] args)505     public static void main(String[] args) {
506         // Set the process name showing in "ps" or "top"
507         Process.setArgV0("com.android.commands.monkey");
508 
509         int resultCode = (new Monkey()).run(args);
510         System.exit(resultCode);
511     }
512 
513     /**
514      * Run the command!
515      *
516      * @param args The command-line arguments
517      * @return Returns a posix-style result code. 0 for no error.
518      */
run(String[] args)519     private int run(String[] args) {
520         // Super-early debugger wait
521         for (String s : args) {
522             if ("--wait-dbg".equals(s)) {
523                 Debug.waitForDebugger();
524             }
525         }
526 
527         // Default values for some command-line options
528         mVerbose = 0;
529         mCount = 1000;
530         mSeed = 0;
531         mThrottle = 0;
532 
533         // prepare for command-line processing
534         mArgs = args;
535         mNextArg = 0;
536 
537         // set a positive value, indicating none of the factors is provided yet
538         for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
539             mFactors[i] = 1.0f;
540         }
541 
542         if (!processOptions()) {
543             return -1;
544         }
545 
546         if (!loadPackageLists()) {
547             return -1;
548         }
549 
550         // now set up additional data in preparation for launch
551         if (mMainCategories.size() == 0) {
552             mMainCategories.add(Intent.CATEGORY_LAUNCHER);
553             mMainCategories.add(Intent.CATEGORY_MONKEY);
554         }
555 
556         if (mSeed == 0) {
557             mSeed = System.currentTimeMillis() + System.identityHashCode(this);
558         }
559 
560         if (mVerbose > 0) {
561             System.out.println(":Monkey: seed=" + mSeed + " count=" + mCount);
562             if (mValidPackages.size() > 0) {
563                 Iterator<String> it = mValidPackages.iterator();
564                 while (it.hasNext()) {
565                     System.out.println(":AllowPackage: " + it.next());
566                 }
567             }
568             if (mInvalidPackages.size() > 0) {
569                 Iterator<String> it = mInvalidPackages.iterator();
570                 while (it.hasNext()) {
571                     System.out.println(":DisallowPackage: " + it.next());
572                 }
573             }
574             if (mMainCategories.size() != 0) {
575                 Iterator<String> it = mMainCategories.iterator();
576                 while (it.hasNext()) {
577                     System.out.println(":IncludeCategory: " + it.next());
578                 }
579             }
580         }
581 
582         if (!checkInternalConfiguration()) {
583             return -2;
584         }
585 
586         if (!getSystemInterfaces()) {
587             return -3;
588         }
589 
590         if (!getMainApps()) {
591             return -4;
592         }
593 
594         mRandom = new Random(mSeed);
595 
596         if (mScriptFileNames != null && mScriptFileNames.size() == 1) {
597             // script mode, ignore other options
598             mEventSource = new MonkeySourceScript(mRandom, mScriptFileNames.get(0), mThrottle,
599                     mRandomizeThrottle, mProfileWaitTime, mDeviceSleepTime);
600             mEventSource.setVerbose(mVerbose);
601 
602             mCountEvents = false;
603         } else if (mScriptFileNames != null && mScriptFileNames.size() > 1) {
604             if (mSetupFileName != null) {
605                 mEventSource = new MonkeySourceRandomScript(mSetupFileName,
606                         mScriptFileNames, mThrottle, mRandomizeThrottle, mRandom,
607                         mProfileWaitTime, mDeviceSleepTime, mRandomizeScript);
608                 mCount++;
609             } else {
610                 mEventSource = new MonkeySourceRandomScript(mScriptFileNames,
611                         mThrottle, mRandomizeThrottle, mRandom,
612                         mProfileWaitTime, mDeviceSleepTime, mRandomizeScript);
613             }
614             mEventSource.setVerbose(mVerbose);
615             mCountEvents = false;
616         } else if (mServerPort != -1) {
617             try {
618                 mEventSource = new MonkeySourceNetwork(mServerPort);
619             } catch (IOException e) {
620                 System.out.println("Error binding to network socket.");
621                 return -5;
622             }
623             mCount = Integer.MAX_VALUE;
624         } else {
625             // random source by default
626             if (mVerbose >= 2) { // check seeding performance
627                 System.out.println("// Seeded: " + mSeed);
628             }
629             mEventSource = new MonkeySourceRandom(mRandom, mMainApps, mThrottle, mRandomizeThrottle);
630             mEventSource.setVerbose(mVerbose);
631             // set any of the factors that has been set
632             for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
633                 if (mFactors[i] <= 0.0f) {
634                     ((MonkeySourceRandom) mEventSource).setFactors(i, mFactors[i]);
635                 }
636             }
637 
638             // in random mode, we start with a random activity
639             ((MonkeySourceRandom) mEventSource).generateActivity();
640         }
641 
642         // validate source generator
643         if (!mEventSource.validate()) {
644             return -5;
645         }
646 
647         // If we're profiling, do it immediately before/after the main monkey
648         // loop
649         if (mGenerateHprof) {
650             signalPersistentProcesses();
651         }
652 
653         mNetworkMonitor.start();
654         int crashedAtCycle = 0;
655         try {
656             crashedAtCycle = runMonkeyCycles();
657         } finally {
658             // Release the rotation lock if it's still held and restore the
659             // original orientation.
660             new MonkeyRotationEvent(Surface.ROTATION_0, false).injectEvent(
661                 mWm, mAm, mVerbose);
662         }
663         mNetworkMonitor.stop();
664 
665         synchronized (this) {
666             if (mRequestAnrTraces) {
667                 reportAnrTraces();
668                 mRequestAnrTraces = false;
669             }
670             if (mRequestAnrBugreport){
671                 System.out.println("Print the anr report");
672                 getBugreport("anr_" + mReportProcessName + "_");
673                 mRequestAnrBugreport = false;
674             }
675             if (mRequestWatchdogBugreport) {
676                 System.out.println("Print the watchdog report");
677                 getBugreport("anr_watchdog_");
678                 mRequestWatchdogBugreport = false;
679             }
680             if (mRequestAppCrashBugreport){
681                 getBugreport("app_crash" + mReportProcessName + "_");
682                 mRequestAppCrashBugreport = false;
683             }
684             if (mRequestDumpsysMemInfo) {
685                 reportDumpsysMemInfo();
686                 mRequestDumpsysMemInfo = false;
687             }
688             if (mRequestPeriodicBugreport){
689                 getBugreport("Bugreport_");
690                 mRequestPeriodicBugreport = false;
691             }
692             if (mWatchdogWaiting) {
693                 mWatchdogWaiting = false;
694                 notifyAll();
695             }
696         }
697 
698         if (mGenerateHprof) {
699             signalPersistentProcesses();
700             if (mVerbose > 0) {
701                 System.out.println("// Generated profiling reports in /data/misc");
702             }
703         }
704 
705         try {
706             mAm.setActivityController(null);
707             mNetworkMonitor.unregister(mAm);
708         } catch (RemoteException e) {
709             // just in case this was latent (after mCount cycles), make sure
710             // we report it
711             if (crashedAtCycle >= mCount) {
712                 crashedAtCycle = mCount - 1;
713             }
714         }
715 
716         // report dropped event stats
717         if (mVerbose > 0) {
718             System.out.print(":Dropped: keys=");
719             System.out.print(mDroppedKeyEvents);
720             System.out.print(" pointers=");
721             System.out.print(mDroppedPointerEvents);
722             System.out.print(" trackballs=");
723             System.out.print(mDroppedTrackballEvents);
724             System.out.print(" flips=");
725             System.out.print(mDroppedFlipEvents);
726             System.out.print(" rotations=");
727             System.out.println(mDroppedRotationEvents);
728         }
729 
730         // report network stats
731         mNetworkMonitor.dump();
732 
733         if (crashedAtCycle < mCount - 1) {
734             System.err.println("** System appears to have crashed at event " + crashedAtCycle
735                     + " of " + mCount + " using seed " + mSeed);
736             return crashedAtCycle;
737         } else {
738             if (mVerbose > 0) {
739                 System.out.println("// Monkey finished");
740             }
741             return 0;
742         }
743     }
744 
745     /**
746      * Process the command-line options
747      *
748      * @return Returns true if options were parsed with no apparent errors.
749      */
processOptions()750     private boolean processOptions() {
751         // quick (throwaway) check for unadorned command
752         if (mArgs.length < 1) {
753             showUsage();
754             return false;
755         }
756 
757         try {
758             String opt;
759             while ((opt = nextOption()) != null) {
760                 if (opt.equals("-s")) {
761                     mSeed = nextOptionLong("Seed");
762                 } else if (opt.equals("-p")) {
763                     mValidPackages.add(nextOptionData());
764                 } else if (opt.equals("-c")) {
765                     mMainCategories.add(nextOptionData());
766                 } else if (opt.equals("-v")) {
767                     mVerbose += 1;
768                 } else if (opt.equals("--ignore-crashes")) {
769                     mIgnoreCrashes = true;
770                 } else if (opt.equals("--ignore-timeouts")) {
771                     mIgnoreTimeouts = true;
772                 } else if (opt.equals("--ignore-security-exceptions")) {
773                     mIgnoreSecurityExceptions = true;
774                 } else if (opt.equals("--monitor-native-crashes")) {
775                     mMonitorNativeCrashes = true;
776                 } else if (opt.equals("--ignore-native-crashes")) {
777                     mIgnoreNativeCrashes = true;
778                 } else if (opt.equals("--kill-process-after-error")) {
779                     mKillProcessAfterError = true;
780                 } else if (opt.equals("--hprof")) {
781                     mGenerateHprof = true;
782                 } else if (opt.equals("--pct-touch")) {
783                     int i = MonkeySourceRandom.FACTOR_TOUCH;
784                     mFactors[i] = -nextOptionLong("touch events percentage");
785                 } else if (opt.equals("--pct-motion")) {
786                     int i = MonkeySourceRandom.FACTOR_MOTION;
787                     mFactors[i] = -nextOptionLong("motion events percentage");
788                 } else if (opt.equals("--pct-trackball")) {
789                     int i = MonkeySourceRandom.FACTOR_TRACKBALL;
790                     mFactors[i] = -nextOptionLong("trackball events percentage");
791                 } else if (opt.equals("--pct-rotation")) {
792                     int i = MonkeySourceRandom.FACTOR_ROTATION;
793                     mFactors[i] = -nextOptionLong("screen rotation events percentage");
794                 } else if (opt.equals("--pct-syskeys")) {
795                     int i = MonkeySourceRandom.FACTOR_SYSOPS;
796                     mFactors[i] = -nextOptionLong("system (key) operations percentage");
797                 } else if (opt.equals("--pct-nav")) {
798                     int i = MonkeySourceRandom.FACTOR_NAV;
799                     mFactors[i] = -nextOptionLong("nav events percentage");
800                 } else if (opt.equals("--pct-majornav")) {
801                     int i = MonkeySourceRandom.FACTOR_MAJORNAV;
802                     mFactors[i] = -nextOptionLong("major nav events percentage");
803                 } else if (opt.equals("--pct-appswitch")) {
804                     int i = MonkeySourceRandom.FACTOR_APPSWITCH;
805                     mFactors[i] = -nextOptionLong("app switch events percentage");
806                 } else if (opt.equals("--pct-flip")) {
807                     int i = MonkeySourceRandom.FACTOR_FLIP;
808                     mFactors[i] = -nextOptionLong("keyboard flip percentage");
809                 } else if (opt.equals("--pct-anyevent")) {
810                     int i = MonkeySourceRandom.FACTOR_ANYTHING;
811                     mFactors[i] = -nextOptionLong("any events percentage");
812                 } else if (opt.equals("--pct-pinchzoom")) {
813                     int i = MonkeySourceRandom.FACTOR_PINCHZOOM;
814                     mFactors[i] = -nextOptionLong("pinch zoom events percentage");
815                 } else if (opt.equals("--pkg-blacklist-file")) {
816                     mPkgBlacklistFile = nextOptionData();
817                 } else if (opt.equals("--pkg-whitelist-file")) {
818                     mPkgWhitelistFile = nextOptionData();
819                 } else if (opt.equals("--throttle")) {
820                     mThrottle = nextOptionLong("delay (in milliseconds) to wait between events");
821                 } else if (opt.equals("--randomize-throttle")) {
822                     mRandomizeThrottle = true;
823                 } else if (opt.equals("--wait-dbg")) {
824                     // do nothing - it's caught at the very start of run()
825                 } else if (opt.equals("--dbg-no-events")) {
826                     mSendNoEvents = true;
827                 } else if (opt.equals("--port")) {
828                     mServerPort = (int) nextOptionLong("Server port to listen on for commands");
829                 } else if (opt.equals("--setup")) {
830                     mSetupFileName = nextOptionData();
831                 } else if (opt.equals("-f")) {
832                     mScriptFileNames.add(nextOptionData());
833                 } else if (opt.equals("--profile-wait")) {
834                     mProfileWaitTime = nextOptionLong("Profile delay" +
835                                 " (in milliseconds) to wait between user action");
836                 } else if (opt.equals("--device-sleep-time")) {
837                     mDeviceSleepTime = nextOptionLong("Device sleep time" +
838                                                       "(in milliseconds)");
839                 } else if (opt.equals("--randomize-script")) {
840                     mRandomizeScript = true;
841                 } else if (opt.equals("--script-log")) {
842                     mScriptLog = true;
843                 } else if (opt.equals("--bugreport")) {
844                     mRequestBugreport = true;
845                 } else if (opt.equals("--periodic-bugreport")){
846                     mGetPeriodicBugreport = true;
847                     mBugreportFrequency = nextOptionLong("Number of iterations");
848                 } else if (opt.equals("-h")) {
849                     showUsage();
850                     return false;
851                 } else {
852                     System.err.println("** Error: Unknown option: " + opt);
853                     showUsage();
854                     return false;
855                 }
856             }
857         } catch (RuntimeException ex) {
858             System.err.println("** Error: " + ex.toString());
859             showUsage();
860             return false;
861         }
862 
863         // If a server port hasn't been specified, we need to specify
864         // a count
865         if (mServerPort == -1) {
866             String countStr = nextArg();
867             if (countStr == null) {
868                 System.err.println("** Error: Count not specified");
869                 showUsage();
870                 return false;
871             }
872 
873             try {
874                 mCount = Integer.parseInt(countStr);
875             } catch (NumberFormatException e) {
876                 System.err.println("** Error: Count is not a number");
877                 showUsage();
878                 return false;
879             }
880         }
881 
882         return true;
883     }
884 
885     /**
886      * Load a list of package names from a file.
887      *
888      * @param fileName The file name, with package names separated by new line.
889      * @param list The destination list.
890      * @return Returns false if any error occurs.
891      */
loadPackageListFromFile(String fileName, HashSet<String> list)892     private static boolean loadPackageListFromFile(String fileName, HashSet<String> list) {
893         BufferedReader reader = null;
894         try {
895             reader = new BufferedReader(new FileReader(fileName));
896             String s;
897             while ((s = reader.readLine()) != null) {
898                 s = s.trim();
899                 if ((s.length() > 0) && (!s.startsWith("#"))) {
900                     list.add(s);
901                 }
902             }
903         } catch (IOException ioe) {
904             System.err.println(ioe);
905             return false;
906         } finally {
907             if (reader != null) {
908                 try {
909                     reader.close();
910                 } catch (IOException ioe) {
911                     System.err.println(ioe);
912                 }
913             }
914         }
915         return true;
916     }
917 
918     /**
919      * Load package blacklist or whitelist (if specified).
920      *
921      * @return Returns false if any error occurs.
922      */
loadPackageLists()923     private boolean loadPackageLists() {
924         if (((mPkgWhitelistFile != null) || (mValidPackages.size() > 0))
925                 && (mPkgBlacklistFile != null)) {
926             System.err.println("** Error: you can not specify a package blacklist "
927                     + "together with a whitelist or individual packages (via -p).");
928             return false;
929         }
930         if ((mPkgWhitelistFile != null)
931                 && (!loadPackageListFromFile(mPkgWhitelistFile, mValidPackages))) {
932             return false;
933         }
934         if ((mPkgBlacklistFile != null)
935                 && (!loadPackageListFromFile(mPkgBlacklistFile, mInvalidPackages))) {
936             return false;
937         }
938         return true;
939     }
940 
941     /**
942      * Check for any internal configuration (primarily build-time) errors.
943      *
944      * @return Returns true if ready to rock.
945      */
checkInternalConfiguration()946     private boolean checkInternalConfiguration() {
947         return true;
948     }
949 
950     /**
951      * Attach to the required system interfaces.
952      *
953      * @return Returns true if all system interfaces were available.
954      */
getSystemInterfaces()955     private boolean getSystemInterfaces() {
956         mAm = ActivityManagerNative.getDefault();
957         if (mAm == null) {
958             System.err.println("** Error: Unable to connect to activity manager; is the system "
959                     + "running?");
960             return false;
961         }
962 
963         mWm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
964         if (mWm == null) {
965             System.err.println("** Error: Unable to connect to window manager; is the system "
966                     + "running?");
967             return false;
968         }
969 
970         mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
971         if (mPm == null) {
972             System.err.println("** Error: Unable to connect to package manager; is the system "
973                     + "running?");
974             return false;
975         }
976 
977         try {
978             mAm.setActivityController(new ActivityController());
979             mNetworkMonitor.register(mAm);
980         } catch (RemoteException e) {
981             System.err.println("** Failed talking with activity manager!");
982             return false;
983         }
984 
985         return true;
986     }
987 
988     /**
989      * Using the restrictions provided (categories & packages), generate a list
990      * of activities that we can actually switch to.
991      *
992      * @return Returns true if it could successfully build a list of target
993      *         activities
994      */
getMainApps()995     private boolean getMainApps() {
996         try {
997             final int N = mMainCategories.size();
998             for (int i = 0; i < N; i++) {
999                 Intent intent = new Intent(Intent.ACTION_MAIN);
1000                 String category = mMainCategories.get(i);
1001                 if (category.length() > 0) {
1002                     intent.addCategory(category);
1003                 }
1004                 List<ResolveInfo> mainApps = mPm.queryIntentActivities(intent, null, 0,
1005                         UserHandle.myUserId());
1006                 if (mainApps == null || mainApps.size() == 0) {
1007                     System.err.println("// Warning: no activities found for category " + category);
1008                     continue;
1009                 }
1010                 if (mVerbose >= 2) { // very verbose
1011                     System.out.println("// Selecting main activities from category " + category);
1012                 }
1013                 final int NA = mainApps.size();
1014                 for (int a = 0; a < NA; a++) {
1015                     ResolveInfo r = mainApps.get(a);
1016                     String packageName = r.activityInfo.applicationInfo.packageName;
1017                     if (checkEnteringPackage(packageName)) {
1018                         if (mVerbose >= 2) { // very verbose
1019                             System.out.println("//   + Using main activity " + r.activityInfo.name
1020                                     + " (from package " + packageName + ")");
1021                         }
1022                         mMainApps.add(new ComponentName(packageName, r.activityInfo.name));
1023                     } else {
1024                         if (mVerbose >= 3) { // very very verbose
1025                             System.out.println("//   - NOT USING main activity "
1026                                     + r.activityInfo.name + " (from package " + packageName + ")");
1027                         }
1028                     }
1029                 }
1030             }
1031         } catch (RemoteException e) {
1032             System.err.println("** Failed talking with package manager!");
1033             return false;
1034         }
1035 
1036         if (mMainApps.size() == 0) {
1037             System.out.println("** No activities found to run, monkey aborted.");
1038             return false;
1039         }
1040 
1041         return true;
1042     }
1043 
1044     /**
1045      * Run mCount cycles and see if we hit any crashers.
1046      * <p>
1047      * TODO: Meta state on keys
1048      *
1049      * @return Returns the last cycle which executed. If the value == mCount, no
1050      *         errors detected.
1051      */
runMonkeyCycles()1052     private int runMonkeyCycles() {
1053         int eventCounter = 0;
1054         int cycleCounter = 0;
1055 
1056         boolean shouldReportAnrTraces = false;
1057         boolean shouldReportDumpsysMemInfo = false;
1058         boolean shouldAbort = false;
1059         boolean systemCrashed = false;
1060 
1061         // TO DO : The count should apply to each of the script file.
1062         while (!systemCrashed && cycleCounter < mCount) {
1063             synchronized (this) {
1064                 if (mRequestProcRank) {
1065                     reportProcRank();
1066                     mRequestProcRank = false;
1067                 }
1068                 if (mRequestAnrTraces) {
1069                     mRequestAnrTraces = false;
1070                     shouldReportAnrTraces = true;
1071                 }
1072                 if (mRequestAnrBugreport){
1073                     getBugreport("anr_" + mReportProcessName + "_");
1074                     mRequestAnrBugreport = false;
1075                 }
1076                 if (mRequestWatchdogBugreport) {
1077                     System.out.println("Print the watchdog report");
1078                     getBugreport("anr_watchdog_");
1079                     mRequestWatchdogBugreport = false;
1080                 }
1081                 if (mRequestAppCrashBugreport){
1082                     getBugreport("app_crash" + mReportProcessName + "_");
1083                     mRequestAppCrashBugreport = false;
1084                 }
1085                 if (mRequestPeriodicBugreport){
1086                     getBugreport("Bugreport_");
1087                     mRequestPeriodicBugreport = false;
1088                 }
1089                 if (mRequestDumpsysMemInfo) {
1090                     mRequestDumpsysMemInfo = false;
1091                     shouldReportDumpsysMemInfo = true;
1092                 }
1093                 if (mMonitorNativeCrashes) {
1094                     // first time through, when eventCounter == 0, just set up
1095                     // the watcher (ignore the error)
1096                     if (checkNativeCrashes() && (eventCounter > 0)) {
1097                         System.out.println("** New native crash detected.");
1098                         if (mRequestBugreport) {
1099                             getBugreport("native_crash_");
1100                         }
1101                         mAbort = mAbort || !mIgnoreNativeCrashes || mKillProcessAfterError;
1102                     }
1103                 }
1104                 if (mAbort) {
1105                     shouldAbort = true;
1106                 }
1107                 if (mWatchdogWaiting) {
1108                     mWatchdogWaiting = false;
1109                     notifyAll();
1110                 }
1111             }
1112 
1113             // Report ANR, dumpsys after releasing lock on this.
1114             // This ensures the availability of the lock to Activity controller's appNotResponding
1115             if (shouldReportAnrTraces) {
1116                shouldReportAnrTraces = false;
1117                reportAnrTraces();
1118             }
1119 
1120             if (shouldReportDumpsysMemInfo) {
1121                shouldReportDumpsysMemInfo = false;
1122                reportDumpsysMemInfo();
1123             }
1124 
1125             if (shouldAbort) {
1126                shouldAbort = false;
1127                System.out.println("** Monkey aborted due to error.");
1128                System.out.println("Events injected: " + eventCounter);
1129                return eventCounter;
1130             }
1131 
1132             // In this debugging mode, we never send any events. This is
1133             // primarily here so you can manually test the package or category
1134             // limits, while manually exercising the system.
1135             if (mSendNoEvents) {
1136                 eventCounter++;
1137                 cycleCounter++;
1138                 continue;
1139             }
1140 
1141             if ((mVerbose > 0) && (eventCounter % 100) == 0 && eventCounter != 0) {
1142                 String calendarTime = MonkeyUtils.toCalendarTime(System.currentTimeMillis());
1143                 long systemUpTime = SystemClock.elapsedRealtime();
1144                 System.out.println("    //[calendar_time:" + calendarTime + " system_uptime:"
1145                                    + systemUpTime + "]");
1146                 System.out.println("    // Sending event #" + eventCounter);
1147             }
1148 
1149             MonkeyEvent ev = mEventSource.getNextEvent();
1150             if (ev != null) {
1151                 int injectCode = ev.injectEvent(mWm, mAm, mVerbose);
1152                 if (injectCode == MonkeyEvent.INJECT_FAIL) {
1153                     System.out.println("    // Injection Failed");
1154                     if (ev instanceof MonkeyKeyEvent) {
1155                         mDroppedKeyEvents++;
1156                     } else if (ev instanceof MonkeyMotionEvent) {
1157                         mDroppedPointerEvents++;
1158                     } else if (ev instanceof MonkeyFlipEvent) {
1159                         mDroppedFlipEvents++;
1160                     } else if (ev instanceof MonkeyRotationEvent) {
1161                         mDroppedRotationEvents++;
1162                     }
1163                 } else if (injectCode == MonkeyEvent.INJECT_ERROR_REMOTE_EXCEPTION) {
1164                     systemCrashed = true;
1165                     System.err.println("** Error: RemoteException while injecting event.");
1166                 } else if (injectCode == MonkeyEvent.INJECT_ERROR_SECURITY_EXCEPTION) {
1167                     systemCrashed = !mIgnoreSecurityExceptions;
1168                     if (systemCrashed) {
1169                         System.err.println("** Error: SecurityException while injecting event.");
1170                     }
1171                 }
1172 
1173                 // Don't count throttling as an event.
1174                 if (!(ev instanceof MonkeyThrottleEvent)) {
1175                     eventCounter++;
1176                     if (mCountEvents) {
1177                         cycleCounter++;
1178                     }
1179                 }
1180             } else {
1181                 if (!mCountEvents) {
1182                     cycleCounter++;
1183                     writeScriptLog(cycleCounter);
1184                     //Capture the bugreport after n iteration
1185                     if (mGetPeriodicBugreport) {
1186                         if ((cycleCounter % mBugreportFrequency) == 0) {
1187                             mRequestPeriodicBugreport = true;
1188                         }
1189                     }
1190                 } else {
1191                     // Event Source has signaled that we have no more events to process
1192                     break;
1193                 }
1194             }
1195         }
1196         System.out.println("Events injected: " + eventCounter);
1197         return eventCounter;
1198     }
1199 
1200     /**
1201      * Send SIGNAL_USR1 to all processes. This will generate large (5mb)
1202      * profiling reports in data/misc, so use with care.
1203      */
signalPersistentProcesses()1204     private void signalPersistentProcesses() {
1205         try {
1206             mAm.signalPersistentProcesses(Process.SIGNAL_USR1);
1207 
1208             synchronized (this) {
1209                 wait(2000);
1210             }
1211         } catch (RemoteException e) {
1212             System.err.println("** Failed talking with activity manager!");
1213         } catch (InterruptedException e) {
1214         }
1215     }
1216 
1217     /**
1218      * Watch for appearance of new tombstone files, which indicate native
1219      * crashes.
1220      *
1221      * @return Returns true if new files have appeared in the list
1222      */
checkNativeCrashes()1223     private boolean checkNativeCrashes() {
1224         String[] tombstones = TOMBSTONES_PATH.list();
1225 
1226         // shortcut path for usually empty directory, so we don't waste even
1227         // more objects
1228         if ((tombstones == null) || (tombstones.length == 0)) {
1229             mTombstones = null;
1230             return false;
1231         }
1232 
1233         // use set logic to look for new files
1234         HashSet<String> newStones = new HashSet<String>();
1235         for (String x : tombstones) {
1236             newStones.add(x);
1237         }
1238 
1239         boolean result = (mTombstones == null) || !mTombstones.containsAll(newStones);
1240 
1241         // keep the new list for the next time
1242         mTombstones = newStones;
1243 
1244         return result;
1245     }
1246 
1247     /**
1248      * Return the next command line option. This has a number of special cases
1249      * which closely, but not exactly, follow the POSIX command line options
1250      * patterns:
1251      *
1252      * <pre>
1253      * -- means to stop processing additional options
1254      * -z means option z
1255      * -z ARGS means option z with (non-optional) arguments ARGS
1256      * -zARGS means option z with (optional) arguments ARGS
1257      * --zz means option zz
1258      * --zz ARGS means option zz with (non-optional) arguments ARGS
1259      * </pre>
1260      *
1261      * Note that you cannot combine single letter options; -abc != -a -b -c
1262      *
1263      * @return Returns the option string, or null if there are no more options.
1264      */
nextOption()1265     private String nextOption() {
1266         if (mNextArg >= mArgs.length) {
1267             return null;
1268         }
1269         String arg = mArgs[mNextArg];
1270         if (!arg.startsWith("-")) {
1271             return null;
1272         }
1273         mNextArg++;
1274         if (arg.equals("--")) {
1275             return null;
1276         }
1277         if (arg.length() > 1 && arg.charAt(1) != '-') {
1278             if (arg.length() > 2) {
1279                 mCurArgData = arg.substring(2);
1280                 return arg.substring(0, 2);
1281             } else {
1282                 mCurArgData = null;
1283                 return arg;
1284             }
1285         }
1286         mCurArgData = null;
1287         return arg;
1288     }
1289 
1290     /**
1291      * Return the next data associated with the current option.
1292      *
1293      * @return Returns the data string, or null of there are no more arguments.
1294      */
nextOptionData()1295     private String nextOptionData() {
1296         if (mCurArgData != null) {
1297             return mCurArgData;
1298         }
1299         if (mNextArg >= mArgs.length) {
1300             return null;
1301         }
1302         String data = mArgs[mNextArg];
1303         mNextArg++;
1304         return data;
1305     }
1306 
1307     /**
1308      * Returns a long converted from the next data argument, with error handling
1309      * if not available.
1310      *
1311      * @param opt The name of the option.
1312      * @return Returns a long converted from the argument.
1313      */
nextOptionLong(final String opt)1314     private long nextOptionLong(final String opt) {
1315         long result;
1316         try {
1317             result = Long.parseLong(nextOptionData());
1318         } catch (NumberFormatException e) {
1319             System.err.println("** Error: " + opt + " is not a number");
1320             throw e;
1321         }
1322         return result;
1323     }
1324 
1325     /**
1326      * Return the next argument on the command line.
1327      *
1328      * @return Returns the argument string, or null if we have reached the end.
1329      */
nextArg()1330     private String nextArg() {
1331         if (mNextArg >= mArgs.length) {
1332             return null;
1333         }
1334         String arg = mArgs[mNextArg];
1335         mNextArg++;
1336         return arg;
1337     }
1338 
1339     /**
1340      * Print how to use this command.
1341      */
showUsage()1342     private void showUsage() {
1343         StringBuffer usage = new StringBuffer();
1344         usage.append("usage: monkey [-p ALLOWED_PACKAGE [-p ALLOWED_PACKAGE] ...]\n");
1345         usage.append("              [-c MAIN_CATEGORY [-c MAIN_CATEGORY] ...]\n");
1346         usage.append("              [--ignore-crashes] [--ignore-timeouts]\n");
1347         usage.append("              [--ignore-security-exceptions]\n");
1348         usage.append("              [--monitor-native-crashes] [--ignore-native-crashes]\n");
1349         usage.append("              [--kill-process-after-error] [--hprof]\n");
1350         usage.append("              [--pct-touch PERCENT] [--pct-motion PERCENT]\n");
1351         usage.append("              [--pct-trackball PERCENT] [--pct-syskeys PERCENT]\n");
1352         usage.append("              [--pct-nav PERCENT] [--pct-majornav PERCENT]\n");
1353         usage.append("              [--pct-appswitch PERCENT] [--pct-flip PERCENT]\n");
1354         usage.append("              [--pct-anyevent PERCENT] [--pct-pinchzoom PERCENT]\n");
1355         usage.append("              [--pkg-blacklist-file PACKAGE_BLACKLIST_FILE]\n");
1356         usage.append("              [--pkg-whitelist-file PACKAGE_WHITELIST_FILE]\n");
1357         usage.append("              [--wait-dbg] [--dbg-no-events]\n");
1358         usage.append("              [--setup scriptfile] [-f scriptfile [-f scriptfile] ...]\n");
1359         usage.append("              [--port port]\n");
1360         usage.append("              [-s SEED] [-v [-v] ...]\n");
1361         usage.append("              [--throttle MILLISEC] [--randomize-throttle]\n");
1362         usage.append("              [--profile-wait MILLISEC]\n");
1363         usage.append("              [--device-sleep-time MILLISEC]\n");
1364         usage.append("              [--randomize-script]\n");
1365         usage.append("              [--script-log]\n");
1366         usage.append("              [--bugreport]\n");
1367         usage.append("              [--periodic-bugreport]\n");
1368         usage.append("              COUNT\n");
1369         System.err.println(usage.toString());
1370     }
1371 }
1372