Fields Summary |
---|
private static final int | DEBUG_ALLOW_ANY_STARTSMonkey Debugging/Dev Support
All values should be zero when checking in. |
private static final int | DEBUG_ALLOW_ANY_RESTARTS |
private android.app.IActivityManager | mAm |
private android.view.IWindowManager | mWm |
private android.content.pm.IPackageManager | mPm |
private String[] | mArgsCommand line arguments |
private int | mNextArgCurrent argument being parsed |
private String | mCurArgDataData of current argument |
private int | mVerboseRunning in verbose output mode? 1= verbose, 2=very verbose |
private boolean | mIgnoreCrashesIgnore any application crashes while running? |
private boolean | mIgnoreTimeoutsIgnore any not responding timeouts while running? |
private boolean | mIgnoreSecurityExceptions(The activity launch still fails, but we keep pluggin' away) |
private boolean | mMonitorNativeCrashesMonitor /data/tombstones and stop the monkey if new files appear. |
private boolean | mSendNoEventsSend no events. Use with long throttle-time to watch user operations |
private boolean | mAbortThis is set when we would like to abort the running of the monkey. |
private boolean | mRequestAnrTracesThis is set by the ActivityWatcher thread to request collection of ANR trace files |
private boolean | mRequestDumpsysMemInfoThis is set by the ActivityWatcher thread to request a "dumpsys meminfo" |
private boolean | mKillProcessAfterErrorKill the process after a timeout or crash. |
private boolean | mGenerateHprofGenerate hprof reports before/after monkey runs |
private HashSet | mValidPackagesPackages we are allowed to run, or empty if no restriction. |
ArrayList | mMainCategoriesCategories we are allowed to launch |
private ArrayList | mMainAppsApplications we can switch to. |
long | mThrottleThe delay between event inputs |
int | mCountThe number of iterations |
long | mSeedThe random number seed |
long | mDroppedKeyEventsDropped-event statistics |
long | mDroppedPointerEvents |
long | mDroppedTrackballEvents |
long | mDroppedFlipEvents |
private String | mScriptFileNamea filename to the script (if any) |
private static final File | TOMBSTONES_PATH |
private HashSet | mTombstones |
float[] | mFactors |
MonkeyEventSource | mEventSource |
Methods Summary |
---|
private boolean | checkInternalConfiguration()Check for any internal configuration (primarily build-time) errors.
// Check KEYCODE name array, make sure it's up to date.
String lastKeyName = null;
try {
lastKeyName = MonkeySourceRandom.getLastKeyName();
} catch (RuntimeException e) {
}
if (!"TAG_LAST_KEYCODE".equals(lastKeyName)) {
System.err.println("** Error: Key names array malformed (internal error).");
return false;
}
return true;
|
private boolean | checkNativeCrashes()Watch for appearance of new tombstone files, which indicate native crashes.
String[] tombstones = TOMBSTONES_PATH.list();
// shortcut path for usually empty directory, so we don't waste even more objects
if ((tombstones == null) || (tombstones.length == 0)) {
mTombstones = null;
return false;
}
// use set logic to look for new files
HashSet<String> newStones = new HashSet<String>();
for (String x : tombstones) {
newStones.add(x);
}
boolean result = (mTombstones == null) || !mTombstones.containsAll(newStones);
// keep the new list for the next time
mTombstones = newStones;
return result;
|
private void | commandLineReport(java.lang.String reportName, java.lang.String command)Print report from a single command line.
System.err.println(reportName + ":");
Runtime rt = Runtime.getRuntime();
try {
// Process must be fully qualified here because android.os.Process is used elsewhere
java.lang.Process p = Runtime.getRuntime().exec(command);
// pipe everything from process stdout -> System.err
InputStream inStream = p.getInputStream();
InputStreamReader inReader = new InputStreamReader(inStream);
BufferedReader inBuffer = new BufferedReader(inReader);
String s;
while ((s = inBuffer.readLine()) != null) {
System.err.println(s);
}
int status = p.waitFor();
System.err.println("// " + reportName + " status was " + status);
} catch (Exception e) {
System.err.println("// Exception from " + reportName + ":");
System.err.println(e.toString());
}
|
private boolean | getMainApps()Using the restrictions provided (categories & packages), generate a list of activities
that we can actually switch to.
try {
final int N = mMainCategories.size();
for (int i = 0; i< N; i++) {
Intent intent = new Intent(Intent.ACTION_MAIN);
String category = mMainCategories.get(i);
if (category.length() > 0) {
intent.addCategory(category);
}
List<ResolveInfo> mainApps = mPm.queryIntentActivities(intent, null, 0);
if (mainApps == null || mainApps.size() == 0) {
System.err.println("// Warning: no activities found for category " + category);
continue;
}
if (mVerbose >= 2) { // very verbose
System.out.println("// Selecting main activities from category " + category);
}
final int NA = mainApps.size();
for (int a = 0; a < NA; a++) {
ResolveInfo r = mainApps.get(a);
if (mValidPackages.size() == 0 ||
mValidPackages.contains(r.activityInfo.applicationInfo.packageName)) {
if (mVerbose >= 2) { // very verbose
System.out.println("// + Using main activity "
+ r.activityInfo.name
+ " (from package "
+ r.activityInfo.applicationInfo.packageName
+ ")");
}
mMainApps.add(new ComponentName(
r.activityInfo.applicationInfo.packageName,
r.activityInfo.name));
} else {
if (mVerbose >= 3) { // very very verbose
System.out.println("// - NOT USING main activity "
+ r.activityInfo.name
+ " (from package "
+ r.activityInfo.applicationInfo.packageName
+ ")");
}
}
}
}
} catch (RemoteException e) {
System.err.println("** Failed talking with package manager!");
return false;
}
if (mMainApps.size() == 0) {
System.out.println("** No activities found to run, monkey aborted.");
return false;
}
return true;
|
private boolean | getSystemInterfaces()Attach to the required system interfaces.
mAm = ActivityManagerNative.getDefault();
if (mAm == null) {
System.err.println("** Error: Unable to connect to activity manager; is the system running?");
return false;
}
mWm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
if (mWm == null) {
System.err.println("** Error: Unable to connect to window manager; is the system running?");
return false;
}
mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
if (mPm == null) {
System.err.println("** Error: Unable to connect to package manager; is the system running?");
return false;
}
try {
mAm.setActivityWatcher(new ActivityWatcher());
} catch (RemoteException e) {
System.err.println("** Failed talking with activity manager!");
return false;
}
return true;
|
public static void | main(java.lang.String[] args)Command-line entry point.
int resultCode = (new Monkey()).run(args);
System.exit(resultCode);
|
private java.lang.String | nextArg()Return the next argument on the command line.
if (mNextArg >= mArgs.length) {
return null;
}
String arg = mArgs[mNextArg];
mNextArg++;
return arg;
|
private java.lang.String | nextOption()Return the next command line option. This has a number of special cases which
closely, but not exactly, follow the POSIX command line options patterns:
-- means to stop processing additional options
-z means option z
-z ARGS means option z with (non-optional) arguments ARGS
-zARGS means option z with (optional) arguments ARGS
--zz means option zz
--zz ARGS means option zz with (non-optional) arguments ARGS
Note that you cannot combine single letter options; -abc != -a -b -c
if (mNextArg >= mArgs.length) {
return null;
}
String arg = mArgs[mNextArg];
if (!arg.startsWith("-")) {
return null;
}
mNextArg++;
if (arg.equals("--")) {
return null;
}
if (arg.length() > 1 && arg.charAt(1) != '-") {
if (arg.length() > 2) {
mCurArgData = arg.substring(2);
return arg.substring(0, 2);
} else {
mCurArgData = null;
return arg;
}
}
mCurArgData = null;
return arg;
|
private java.lang.String | nextOptionData()Return the next data associated with the current option.
if (mCurArgData != null) {
return mCurArgData;
}
if (mNextArg >= mArgs.length) {
return null;
}
String data = mArgs[mNextArg];
mNextArg++;
return data;
|
private long | nextOptionLong(java.lang.String opt)Returns a long converted from the next data argument, with error handling if not available.
long result;
try {
result = Long.parseLong(nextOptionData());
} catch (NumberFormatException e) {
System.err.println("** Error: " + opt + " is not a number");
throw e;
}
return result;
|
private boolean | processOptions()Process the command-line options
// quick (throwaway) check for unadorned command
if (mArgs.length < 1) {
showUsage();
return false;
}
try {
String opt;
while ((opt = nextOption()) != null) {
if (opt.equals("-s")) {
mSeed = nextOptionLong("Seed");
} else if (opt.equals("-p")) {
mValidPackages.add(nextOptionData());
} else if (opt.equals("-c")) {
mMainCategories.add(nextOptionData());
} else if (opt.equals("-v")) {
mVerbose += 1;
} else if (opt.equals("--ignore-crashes")) {
mIgnoreCrashes = true;
} else if (opt.equals("--ignore-timeouts")) {
mIgnoreTimeouts = true;
} else if (opt.equals("--ignore-security-exceptions")) {
mIgnoreSecurityExceptions = true;
} else if (opt.equals("--monitor-native-crashes")) {
mMonitorNativeCrashes = true;
} else if (opt.equals("--kill-process-after-error")) {
mKillProcessAfterError = true;
} else if (opt.equals("--hprof")) {
mGenerateHprof = true;
} else if (opt.equals("--pct-touch")) {
mFactors[MonkeySourceRandom.FACTOR_TOUCH] =
-nextOptionLong("touch events percentage");
} else if (opt.equals("--pct-motion")) {
mFactors[MonkeySourceRandom.FACTOR_MOTION] =
-nextOptionLong("motion events percentage");
} else if (opt.equals("--pct-trackball")) {
mFactors[MonkeySourceRandom.FACTOR_TRACKBALL] =
-nextOptionLong("trackball events percentage");
} else if (opt.equals("--pct-nav")) {
mFactors[MonkeySourceRandom.FACTOR_NAV] =
-nextOptionLong("nav events percentage");
} else if (opt.equals("--pct-majornav")) {
mFactors[MonkeySourceRandom.FACTOR_MAJORNAV] =
-nextOptionLong("major nav events percentage");
} else if (opt.equals("--pct-appswitch")) {
mFactors[MonkeySourceRandom.FACTOR_APPSWITCH] =
-nextOptionLong("app switch events percentage");
} else if (opt.equals("--pct-flip")) {
mFactors[MonkeySourceRandom.FACTOR_FLIP] =
-nextOptionLong("keyboard flip percentage");
} else if (opt.equals("--pct-anyevent")) {
mFactors[MonkeySourceRandom.FACTOR_ANYTHING] =
-nextOptionLong("any events percentage");
} else if (opt.equals("--throttle")) {
mThrottle = nextOptionLong("delay (in milliseconds) to wait between events");
} else if (opt.equals("--wait-dbg")) {
// do nothing - it's caught at the very start of run()
} else if (opt.equals("--dbg-no-events")) {
mSendNoEvents = true;
} else if (opt.equals("-f")) {
mScriptFileName = nextOptionData();
} else if (opt.equals("-h")) {
showUsage();
return false;
} else {
System.err.println("** Error: Unknown option: " + opt);
showUsage();
return false;
}
}
} catch (RuntimeException ex) {
System.err.println("** Error: " + ex.toString());
showUsage();
return false;
}
String countStr = nextArg();
if (countStr == null) {
System.err.println("** Error: Count not specified");
showUsage();
return false;
}
try {
mCount = Integer.parseInt(countStr);
} catch (NumberFormatException e) {
System.err.println("** Error: Count is not a number");
showUsage();
return false;
}
return true;
|
private void | reportAnrTraces()Run "cat /data/anr/traces.txt". Wait about 5 seconds first, to let the asynchronous
report writing complete.
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
}
commandLineReport("anr traces", "cat /data/anr/traces.txt");
|
private void | reportDumpsysMemInfo()Run "dumpsys meminfo"
NOTE: You cannot perform a dumpsys call from the ActivityWatcher callback, as it will
deadlock. This should only be called from the main loop of the monkey.
commandLineReport("meminfo", "dumpsys meminfo");
|
private void | reportProcRank()Run the procrank tool to insert system status information into the debug report.
commandLineReport("procrank", "procrank");
|
private int | run(java.lang.String[] args)Run the command!
// Super-early debugger wait
for (String s : args) {
if ("--wait-dbg".equals(s)) {
Debug.waitForDebugger();
}
}
// Default values for some command-line options
mVerbose = 0;
mCount = 1000;
mSeed = 0;
mThrottle = 0;
// prepare for command-line processing
mArgs = args;
mNextArg = 0;
//set a positive value, indicating none of the factors is provided yet
for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
mFactors[i] = 1.0f;
}
if (!processOptions()) {
return -1;
}
// now set up additional data in preparation for launch
if (mMainCategories.size() == 0) {
mMainCategories.add(Intent.CATEGORY_LAUNCHER);
mMainCategories.add(Intent.CATEGORY_MONKEY);
}
if (mVerbose > 0) {
System.out.println(":Monkey: seed=" + mSeed + " count=" + mCount);
if (mValidPackages.size() > 0) {
Iterator<String> it = mValidPackages.iterator();
while (it.hasNext()) {
System.out.println(":AllowPackage: " + it.next());
}
}
if (mMainCategories.size() != 0) {
Iterator<String> it = mMainCategories.iterator();
while (it.hasNext()) {
System.out.println(":IncludeCategory: " + it.next());
}
}
}
if (!checkInternalConfiguration()) {
return -2;
}
if (!getSystemInterfaces()) {
return -3;
}
if (!getMainApps()) {
return -4;
}
if (mScriptFileName != null) {
// script mode, ignore other options
mEventSource = new MonkeySourceScript(mScriptFileName);
mEventSource.setVerbose(mVerbose);
} else {
// random source by default
if (mVerbose >= 2) { // check seeding performance
System.out.println("// Seeded: " + mSeed);
}
mEventSource = new MonkeySourceRandom(mSeed, mMainApps, mThrottle);
mEventSource.setVerbose(mVerbose);
//set any of the factors that has been set
for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
if (mFactors[i] <= 0.0f) {
((MonkeySourceRandom) mEventSource).setFactors(i, mFactors[i]);
}
}
//in random mode, we start with a random activity
((MonkeySourceRandom) mEventSource).generateActivity();
}
//validate source generator
if (!mEventSource.validate()) {
return -5;
}
if (mScriptFileName != null) {
// in random mode, count is the number of single events
// while in script mode, count is the number of repetition
// for a sequence of events, so we need do multiply the length of
// that sequence
mCount = mCount * ((MonkeySourceScript) mEventSource)
.getOneRoundEventCount();
}
// If we're profiling, do it immediately before/after the main monkey loop
if (mGenerateHprof) {
signalPersistentProcesses();
}
int crashedAtCycle = runMonkeyCycles();
synchronized (this) {
if (mRequestAnrTraces) {
reportAnrTraces();
mRequestAnrTraces = false;
}
if (mRequestDumpsysMemInfo) {
reportDumpsysMemInfo();
mRequestDumpsysMemInfo = false;
}
}
if (mGenerateHprof) {
signalPersistentProcesses();
if (mVerbose > 0) {
System.out.println("// Generated profiling reports in /data/misc");
}
}
try {
mAm.setActivityWatcher(null);
} catch (RemoteException e) {
// just in case this was latent (after mCount cycles), make sure
// we report it
if (crashedAtCycle >= mCount) {
crashedAtCycle = mCount - 1;
}
}
// report dropped event stats
if (mVerbose > 0) {
System.out.print(":Dropped: keys=");
System.out.print(mDroppedKeyEvents);
System.out.print(" pointers=");
System.out.print(mDroppedPointerEvents);
System.out.print(" trackballs=");
System.out.print(mDroppedTrackballEvents);
System.out.print(" flips=");
System.out.println(mDroppedFlipEvents);
}
if (crashedAtCycle < mCount - 1) {
System.err.println("** System appears to have crashed at event "
+ crashedAtCycle + " of " + mCount + " using seed " + mSeed);
return crashedAtCycle;
} else {
if (mVerbose > 0) {
System.out.println("// Monkey finished");
}
return 0;
}
|
private int | runMonkeyCycles()Run mCount cycles and see if we hit any crashers.
TODO: Meta state on keys
int i = 0;
int lastKey = 0;
boolean systemCrashed = false;
while (!systemCrashed && i < mCount) {
synchronized (this) {
if (mRequestAnrTraces) {
reportAnrTraces();
mRequestAnrTraces = false;
}
if (mRequestDumpsysMemInfo) {
reportDumpsysMemInfo();
mRequestDumpsysMemInfo = false;
}
if (mMonitorNativeCrashes) {
// first time through, when i == 0, just set up the watcher (ignore the error)
if (checkNativeCrashes() && (i > 0)) {
System.out.println("** New native crash detected.");
mAbort = mAbort || mKillProcessAfterError;
}
}
if (mAbort) {
System.out.println("** Monkey aborted due to error.");
System.out.println("Events injected: " + i);
return i;
}
}
// In this debugging mode, we never send any events. This is primarily
// here so you can manually test the package or category limits, while manually
// exercising the system.
if (mSendNoEvents) {
i++;
continue;
}
if ((mVerbose > 0) && (i % 100) == 0 && i != 0 && lastKey == 0) {
System.out.println(" // Sending event #" + i);
}
MonkeyEvent ev = mEventSource.getNextEvent();
if (ev != null) {
// We don't want to count throttling as an event.
if (!(ev instanceof MonkeyThrottleEvent)) {
i++;
}
int injectCode = ev.injectEvent(mWm, mAm, mVerbose);
if (injectCode == MonkeyEvent.INJECT_FAIL) {
if (ev instanceof MonkeyKeyEvent) {
mDroppedKeyEvents++;
} else if (ev instanceof MonkeyMotionEvent) {
mDroppedPointerEvents++;
} else if (ev instanceof MonkeyFlipEvent) {
mDroppedFlipEvents++;
}
} else if (injectCode == MonkeyEvent.INJECT_ERROR_REMOTE_EXCEPTION) {
systemCrashed = true;
} else if (injectCode == MonkeyEvent.INJECT_ERROR_SECURITY_EXCEPTION) {
systemCrashed = !mIgnoreSecurityExceptions;
}
}
}
// If we got this far, we succeeded!
return mCount;
|
private void | showUsage()Print how to use this command.
System.err.println("usage: monkey [-p ALLOWED_PACKAGE [-p ALLOWED_PACKAGE] ...]");
System.err.println(" [-c MAIN_CATEGORY [-c MAIN_CATEGORY] ...]");
System.err.println(" [--ignore-crashes] [--ignore-timeouts]");
System.err.println(" [--ignore-security-exceptions] [--monitor-native-crashes]");
System.err.println(" [--kill-process-after-error] [--hprof]");
System.err.println(" [--pct-touch PERCENT] [--pct-motion PERCENT]");
System.err.println(" [--pct-trackball PERCENT] [--pct-syskeys PERCENT]");
System.err.println(" [--pct-nav PERCENT] [--pct-majornav PERCENT]");
System.err.println(" [--pct-appswitch PERCENT] [--pct-flip PERCENT]");
System.err.println(" [--pct-anyevent PERCENT]");
System.err.println(" [--wait-dbg] [--dbg-no-events] [-f scriptfile]");
System.err.println(" [-s SEED] [-v [-v] ...] [--throttle MILLISEC]");
System.err.println(" COUNT");
|
private void | signalPersistentProcesses()Send SIGNAL_USR1 to all processes. This will generate large (5mb) profiling reports
in data/misc, so use with care.
try {
mAm.signalPersistentProcesses(Process.SIGNAL_USR1);
synchronized (this) {
wait(2000);
}
} catch (RemoteException e) {
System.err.println("** Failed talking with activity manager!");
} catch (InterruptedException e) {
}
|