Merge "Fix how the monkey counts events for scripts." into eclair

This commit is contained in:
Eric Rowe
2010-01-13 19:25:21 -08:00
committed by Android (Google) Code Review
4 changed files with 355 additions and 278 deletions

View File

@@ -1,19 +1,18 @@
/** /*
** Copyright 2007, The Android Open Source Project * Copyright 2007, The Android Open Source Project
** *
** Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
** You may obtain a copy of the License at * You may obtain a copy of the License at
** *
** http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
** *
** Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
** limitations under the License. * limitations under the License.
*/ */
package com.android.commands.monkey; package com.android.commands.monkey;
@@ -51,19 +50,25 @@ public class Monkey {
/** /**
* Monkey Debugging/Dev Support * Monkey Debugging/Dev Support
* * <p>
* All values should be zero when checking in. * All values should be zero when checking in.
*/ */
private final static int DEBUG_ALLOW_ANY_STARTS = 0; private final static int DEBUG_ALLOW_ANY_STARTS = 0;
private final static int DEBUG_ALLOW_ANY_RESTARTS = 0; private final static int DEBUG_ALLOW_ANY_RESTARTS = 0;
private IActivityManager mAm; private IActivityManager mAm;
private IWindowManager mWm; private IWindowManager mWm;
private IPackageManager mPm; private IPackageManager mPm;
/** Command line arguments */ /** Command line arguments */
private String[] mArgs; private String[] mArgs;
/** Current argument being parsed */ /** Current argument being parsed */
private int mNextArg; private int mNextArg;
/** Data of current argument */ /** Data of current argument */
private String mCurArgData; private String mCurArgData;
@@ -83,16 +88,28 @@ public class Monkey {
/** Monitor /data/tombstones and stop the monkey if new files appear. */ /** Monitor /data/tombstones and stop the monkey if new files appear. */
private boolean mMonitorNativeCrashes; private boolean mMonitorNativeCrashes;
/** Send no events. Use with long throttle-time to watch user operations */ /** Send no events. Use with long throttle-time to watch user operations */
private boolean mSendNoEvents; private boolean mSendNoEvents;
/** This is set when we would like to abort the running of the monkey. */ /** This is set when we would like to abort the running of the monkey. */
private boolean mAbort; private boolean mAbort;
/** This is set by the ActivityController thread to request collection of ANR trace files */ /**
* Count each event as a cycle. Set to false for scripts so that each time
* through the script increments the count.
*/
private boolean mCountEvents = true;
/**
* This is set by the ActivityController thread to request collection of ANR
* trace files
*/
private boolean mRequestAnrTraces = false; private boolean mRequestAnrTraces = false;
/** This is set by the ActivityController thread to request a "dumpsys meminfo" */ /**
* This is set by the ActivityController thread to request a
* "dumpsys meminfo"
*/
private boolean mRequestDumpsysMemInfo = false; private boolean mRequestDumpsysMemInfo = false;
/** Kill the process after a timeout or crash. */ /** Kill the process after a timeout or crash. */
@@ -103,8 +120,10 @@ public class Monkey {
/** Packages we are allowed to run, or empty if no restriction. */ /** Packages we are allowed to run, or empty if no restriction. */
private HashSet<String> mValidPackages = new HashSet<String>(); private HashSet<String> mValidPackages = new HashSet<String>();
/** Categories we are allowed to launch **/ /** Categories we are allowed to launch **/
ArrayList<String> mMainCategories = new ArrayList<String>(); private ArrayList<String> mMainCategories = new ArrayList<String>();
/** Applications we can switch to. */ /** Applications we can switch to. */
private ArrayList<ComponentName> mMainApps = new ArrayList<ComponentName>(); private ArrayList<ComponentName> mMainApps = new ArrayList<ComponentName>();
@@ -119,8 +138,11 @@ public class Monkey {
/** Dropped-event statistics **/ /** Dropped-event statistics **/
long mDroppedKeyEvents = 0; long mDroppedKeyEvents = 0;
long mDroppedPointerEvents = 0; long mDroppedPointerEvents = 0;
long mDroppedTrackballEvents = 0; long mDroppedTrackballEvents = 0;
long mDroppedFlipEvents = 0; long mDroppedFlipEvents = 0;
/** a filename to the script (if any) **/ /** a filename to the script (if any) **/
@@ -130,14 +152,18 @@ public class Monkey {
private int mServerPort = -1; private int mServerPort = -1;
private static final File TOMBSTONES_PATH = new File("/data/tombstones"); private static final File TOMBSTONES_PATH = new File("/data/tombstones");
private HashSet<String> mTombstones = null; private HashSet<String> mTombstones = null;
float[] mFactors = new float[MonkeySourceRandom.FACTORZ_COUNT]; float[] mFactors = new float[MonkeySourceRandom.FACTORZ_COUNT];
MonkeyEventSource mEventSource; MonkeyEventSource mEventSource;
private MonkeyNetworkMonitor mNetworkMonitor = new MonkeyNetworkMonitor(); private MonkeyNetworkMonitor mNetworkMonitor = new MonkeyNetworkMonitor();
// information on the current activity. // information on the current activity.
public static Intent currentIntent; public static Intent currentIntent;
public static String currentPackage; public static String currentPackage;
/** /**
@@ -147,8 +173,8 @@ public class Monkey {
public boolean activityStarting(Intent intent, String pkg) { public boolean activityStarting(Intent intent, String pkg) {
boolean allow = checkEnteringPackage(pkg) || (DEBUG_ALLOW_ANY_STARTS != 0); boolean allow = checkEnteringPackage(pkg) || (DEBUG_ALLOW_ANY_STARTS != 0);
if (mVerbose > 0) { if (mVerbose > 0) {
System.out.println(" // " + (allow ? "Allowing" : "Rejecting") System.out.println(" // " + (allow ? "Allowing" : "Rejecting") + " start of "
+ " start of " + intent + " in package " + pkg); + intent + " in package " + pkg);
} }
currentPackage = pkg; currentPackage = pkg;
currentIntent = intent; currentIntent = intent;
@@ -161,7 +187,7 @@ public class Monkey {
if (!allow) { if (!allow) {
if (mVerbose > 0) { if (mVerbose > 0) {
System.out.println(" // " + (allow ? "Allowing" : "Rejecting") System.out.println(" // " + (allow ? "Allowing" : "Rejecting")
+ " resume of package " + pkg); + " resume of package " + pkg);
} }
} }
currentPackage = pkg; currentPackage = pkg;
@@ -172,7 +198,8 @@ public class Monkey {
if (pkg == null) { if (pkg == null) {
return true; return true;
} }
// preflight the hash lookup to avoid the cost of hash key generation // preflight the hash lookup to avoid the cost of hash key
// generation
if (mValidPackages.size() == 0) { if (mValidPackages.size() == 0) {
return true; return true;
} else { } else {
@@ -180,26 +207,22 @@ public class Monkey {
} }
} }
public boolean appCrashed(String processName, int pid, String shortMsg, public boolean appCrashed(String processName, int pid, String shortMsg, String longMsg,
String longMsg, byte[] crashData) { byte[] crashData) {
System.err.println("// CRASH: " + processName + " (pid " + pid System.err.println("// CRASH: " + processName + " (pid " + pid + ")");
+ ")");
System.err.println("// Short Msg: " + shortMsg); System.err.println("// Short Msg: " + shortMsg);
System.err.println("// Long Msg: " + longMsg); System.err.println("// Long Msg: " + longMsg);
if (crashData != null) { if (crashData != null) {
try { try {
CrashData cd = new CrashData(new DataInputStream( CrashData cd = new CrashData(new DataInputStream(new ByteArrayInputStream(
new ByteArrayInputStream(crashData))); crashData)));
System.err.println("// Build Label: " System.err.println("// Build Label: " + cd.getBuildData().getFingerprint());
+ cd.getBuildData().getFingerprint());
System.err.println("// Build Changelist: " System.err.println("// Build Changelist: "
+ cd.getBuildData().getIncrementalVersion()); + cd.getBuildData().getIncrementalVersion());
System.err.println("// Build Time: " System.err.println("// Build Time: " + cd.getBuildData().getTime());
+ cd.getBuildData().getTime());
System.err.println("// ID: " + cd.getId()); System.err.println("// ID: " + cd.getId());
System.err.println("// Tag: " + cd.getActivity()); System.err.println("// Tag: " + cd.getActivity());
System.err.println(cd.getThrowableData().toString( System.err.println(cd.getThrowableData().toString("// "));
"// "));
} catch (IOException e) { } catch (IOException e) {
System.err.println("// BAD STACK CRAWL"); System.err.println("// BAD STACK CRAWL");
} }
@@ -215,10 +238,8 @@ public class Monkey {
return false; return false;
} }
public int appNotResponding(String processName, int pid, public int appNotResponding(String processName, int pid, String processStats) {
String processStats) { System.err.println("// NOT RESPONDING: " + processName + " (pid " + pid + ")");
System.err.println("// NOT RESPONDING: " + processName
+ " (pid " + pid + ")");
System.err.println(processStats); System.err.println(processStats);
reportProcRank(); reportProcRank();
synchronized (Monkey.this) { synchronized (Monkey.this) {
@@ -236,15 +257,16 @@ public class Monkey {
} }
/** /**
* Run the procrank tool to insert system status information into the debug report. * Run the procrank tool to insert system status information into the debug
* report.
*/ */
private void reportProcRank() { private void reportProcRank() {
commandLineReport("procrank", "procrank"); commandLineReport("procrank", "procrank");
} }
/** /**
* Run "cat /data/anr/traces.txt". Wait about 5 seconds first, to let the asynchronous * Run "cat /data/anr/traces.txt". Wait about 5 seconds first, to let the
* report writing complete. * asynchronous report writing complete.
*/ */
private void reportAnrTraces() { private void reportAnrTraces() {
try { try {
@@ -256,9 +278,10 @@ public class Monkey {
/** /**
* Run "dumpsys meminfo" * Run "dumpsys meminfo"
* * <p>
* NOTE: You cannot perform a dumpsys call from the ActivityController callback, as it will * NOTE: You cannot perform a dumpsys call from the ActivityController
* deadlock. This should only be called from the main loop of the monkey. * callback, as it will deadlock. This should only be called from the main
* loop of the monkey.
*/ */
private void reportDumpsysMemInfo() { private void reportDumpsysMemInfo() {
commandLineReport("meminfo", "dumpsys meminfo"); commandLineReport("meminfo", "dumpsys meminfo");
@@ -266,16 +289,20 @@ public class Monkey {
/** /**
* Print report from a single command line. * Print report from a single command line.
* @param reportName Simple tag that will print before the report and in various annotations. * <p>
* TODO: Use ProcessBuilder & redirectErrorStream(true) to capture both
* streams (might be important for some command lines)
*
* @param reportName Simple tag that will print before the report and in
* various annotations.
* @param command Command line to execute. * @param command Command line to execute.
* TODO: Use ProcessBuilder & redirectErrorStream(true) to capture both streams (might be
* important for some command lines)
*/ */
private void commandLineReport(String reportName, String command) { private void commandLineReport(String reportName, String command) {
System.err.println(reportName + ":"); System.err.println(reportName + ":");
Runtime rt = Runtime.getRuntime(); Runtime rt = Runtime.getRuntime();
try { try {
// Process must be fully qualified here because android.os.Process is used elsewhere // Process must be fully qualified here because android.os.Process
// is used elsewhere
java.lang.Process p = Runtime.getRuntime().exec(command); java.lang.Process p = Runtime.getRuntime().exec(command);
// pipe everything from process stdout -> System.err // pipe everything from process stdout -> System.err
@@ -312,7 +339,7 @@ public class Monkey {
* Run the command! * Run the command!
* *
* @param args The command-line arguments * @param args The command-line arguments
* @return Returns a posix-style result code. 0 for no error. * @return Returns a posix-style result code. 0 for no error.
*/ */
private int run(String[] args) { private int run(String[] args) {
// Super-early debugger wait // Super-early debugger wait
@@ -332,7 +359,7 @@ public class Monkey {
mArgs = args; mArgs = args;
mNextArg = 0; mNextArg = 0;
//set a positive value, indicating none of the factors is provided yet // set a positive value, indicating none of the factors is provided yet
for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) { for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
mFactors[i] = 1.0f; mFactors[i] = 1.0f;
} }
@@ -379,6 +406,8 @@ public class Monkey {
// script mode, ignore other options // script mode, ignore other options
mEventSource = new MonkeySourceScript(mScriptFileName, mThrottle); mEventSource = new MonkeySourceScript(mScriptFileName, mThrottle);
mEventSource.setVerbose(mVerbose); mEventSource.setVerbose(mVerbose);
mCountEvents = false;
} else if (mServerPort != -1) { } else if (mServerPort != -1) {
try { try {
mEventSource = new MonkeySourceNetwork(mServerPort); mEventSource = new MonkeySourceNetwork(mServerPort);
@@ -389,37 +418,29 @@ public class Monkey {
mCount = Integer.MAX_VALUE; mCount = Integer.MAX_VALUE;
} else { } else {
// random source by default // random source by default
if (mVerbose >= 2) { // check seeding performance if (mVerbose >= 2) { // check seeding performance
System.out.println("// Seeded: " + mSeed); System.out.println("// Seeded: " + mSeed);
} }
mEventSource = new MonkeySourceRandom(mSeed, mMainApps, mThrottle); mEventSource = new MonkeySourceRandom(mSeed, mMainApps, mThrottle);
mEventSource.setVerbose(mVerbose); mEventSource.setVerbose(mVerbose);
//set any of the factors that has been set // set any of the factors that has been set
for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) { for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
if (mFactors[i] <= 0.0f) { if (mFactors[i] <= 0.0f) {
((MonkeySourceRandom) mEventSource).setFactors(i, mFactors[i]); ((MonkeySourceRandom) mEventSource).setFactors(i, mFactors[i]);
} }
} }
//in random mode, we start with a random activity // in random mode, we start with a random activity
((MonkeySourceRandom) mEventSource).generateActivity(); ((MonkeySourceRandom) mEventSource).generateActivity();
} }
//validate source generator // validate source generator
if (!mEventSource.validate()) { if (!mEventSource.validate()) {
return -5; return -5;
} }
if (mScriptFileName != null) { // If we're profiling, do it immediately before/after the main monkey
// in random mode, count is the number of single events // loop
// 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) { if (mGenerateHprof) {
signalPersistentProcesses(); signalPersistentProcesses();
} }
@@ -473,9 +494,9 @@ public class Monkey {
mNetworkMonitor.dump(); mNetworkMonitor.dump();
if (crashedAtCycle < mCount - 1) { if (crashedAtCycle < mCount - 1) {
System.err.println("** System appears to have crashed at event " System.err.println("** System appears to have crashed at event " + crashedAtCycle
+ crashedAtCycle + " of " + mCount + " using seed " + mSeed); + " of " + mCount + " using seed " + mSeed);
return crashedAtCycle; return crashedAtCycle;
} else { } else {
if (mVerbose > 0) { if (mVerbose > 0) {
System.out.println("// Monkey finished"); System.out.println("// Monkey finished");
@@ -520,29 +541,29 @@ public class Monkey {
} else if (opt.equals("--hprof")) { } else if (opt.equals("--hprof")) {
mGenerateHprof = true; mGenerateHprof = true;
} else if (opt.equals("--pct-touch")) { } else if (opt.equals("--pct-touch")) {
mFactors[MonkeySourceRandom.FACTOR_TOUCH] = int i = MonkeySourceRandom.FACTOR_TOUCH;
-nextOptionLong("touch events percentage"); mFactors[i] = -nextOptionLong("touch events percentage");
} else if (opt.equals("--pct-motion")) { } else if (opt.equals("--pct-motion")) {
mFactors[MonkeySourceRandom.FACTOR_MOTION] = int i = MonkeySourceRandom.FACTOR_MOTION;
-nextOptionLong("motion events percentage"); mFactors[i] = -nextOptionLong("motion events percentage");
} else if (opt.equals("--pct-trackball")) { } else if (opt.equals("--pct-trackball")) {
mFactors[MonkeySourceRandom.FACTOR_TRACKBALL] = int i = MonkeySourceRandom.FACTOR_TRACKBALL;
-nextOptionLong("trackball events percentage"); mFactors[i] = -nextOptionLong("trackball events percentage");
} else if (opt.equals("--pct-nav")) { } else if (opt.equals("--pct-nav")) {
mFactors[MonkeySourceRandom.FACTOR_NAV] = int i = MonkeySourceRandom.FACTOR_NAV;
-nextOptionLong("nav events percentage"); mFactors[i] = -nextOptionLong("nav events percentage");
} else if (opt.equals("--pct-majornav")) { } else if (opt.equals("--pct-majornav")) {
mFactors[MonkeySourceRandom.FACTOR_MAJORNAV] = int i = MonkeySourceRandom.FACTOR_MAJORNAV;
-nextOptionLong("major nav events percentage"); mFactors[i] = -nextOptionLong("major nav events percentage");
} else if (opt.equals("--pct-appswitch")) { } else if (opt.equals("--pct-appswitch")) {
mFactors[MonkeySourceRandom.FACTOR_APPSWITCH] = int i = MonkeySourceRandom.FACTOR_APPSWITCH;
-nextOptionLong("app switch events percentage"); mFactors[i] = -nextOptionLong("app switch events percentage");
} else if (opt.equals("--pct-flip")) { } else if (opt.equals("--pct-flip")) {
mFactors[MonkeySourceRandom.FACTOR_FLIP] = int i = MonkeySourceRandom.FACTOR_FLIP;
-nextOptionLong("keyboard flip percentage"); mFactors[i] = -nextOptionLong("keyboard flip percentage");
} else if (opt.equals("--pct-anyevent")) { } else if (opt.equals("--pct-anyevent")) {
mFactors[MonkeySourceRandom.FACTOR_ANYTHING] = int i = MonkeySourceRandom.FACTOR_ANYTHING;
-nextOptionLong("any events percentage"); mFactors[i] = -nextOptionLong("any events percentage");
} else if (opt.equals("--throttle")) { } else if (opt.equals("--throttle")) {
mThrottle = nextOptionLong("delay (in milliseconds) to wait between events"); mThrottle = nextOptionLong("delay (in milliseconds) to wait between events");
} else if (opt.equals("--wait-dbg")) { } else if (opt.equals("--wait-dbg")) {
@@ -619,19 +640,22 @@ public class Monkey {
private boolean getSystemInterfaces() { private boolean getSystemInterfaces() {
mAm = ActivityManagerNative.getDefault(); mAm = ActivityManagerNative.getDefault();
if (mAm == null) { if (mAm == null) {
System.err.println("** Error: Unable to connect to activity manager; is the system running?"); System.err.println("** Error: Unable to connect to activity manager; is the system "
+ "running?");
return false; return false;
} }
mWm = IWindowManager.Stub.asInterface(ServiceManager.getService("window")); mWm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
if (mWm == null) { if (mWm == null) {
System.err.println("** Error: Unable to connect to window manager; is the system running?"); System.err.println("** Error: Unable to connect to window manager; is the system "
+ "running?");
return false; return false;
} }
mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
if (mPm == null) { if (mPm == null) {
System.err.println("** Error: Unable to connect to package manager; is the system running?"); System.err.println("** Error: Unable to connect to package manager; is the system "
+ "running?");
return false; return false;
} }
@@ -647,15 +671,16 @@ public class Monkey {
} }
/** /**
* Using the restrictions provided (categories & packages), generate a list of activities * Using the restrictions provided (categories & packages), generate a list
* that we can actually switch to. * of activities that we can actually switch to.
* *
* @return Returns true if it could successfully build a list of target activities * @return Returns true if it could successfully build a list of target
* activities
*/ */
private boolean getMainApps() { private boolean getMainApps() {
try { try {
final int N = mMainCategories.size(); final int N = mMainCategories.size();
for (int i = 0; i< N; i++) { for (int i = 0; i < N; i++) {
Intent intent = new Intent(Intent.ACTION_MAIN); Intent intent = new Intent(Intent.ACTION_MAIN);
String category = mMainCategories.get(i); String category = mMainCategories.get(i);
if (category.length() > 0) { if (category.length() > 0) {
@@ -666,31 +691,26 @@ public class Monkey {
System.err.println("// Warning: no activities found for category " + category); System.err.println("// Warning: no activities found for category " + category);
continue; continue;
} }
if (mVerbose >= 2) { // very verbose if (mVerbose >= 2) { // very verbose
System.out.println("// Selecting main activities from category " + category); System.out.println("// Selecting main activities from category " + category);
} }
final int NA = mainApps.size(); final int NA = mainApps.size();
for (int a = 0; a < NA; a++) { for (int a = 0; a < NA; a++) {
ResolveInfo r = mainApps.get(a); ResolveInfo r = mainApps.get(a);
if (mValidPackages.size() == 0 || if (mValidPackages.size() == 0
mValidPackages.contains(r.activityInfo.applicationInfo.packageName)) { || mValidPackages.contains(r.activityInfo.applicationInfo.packageName)) {
if (mVerbose >= 2) { // very verbose if (mVerbose >= 2) { // very verbose
System.out.println("// + Using main activity " System.out.println("// + Using main activity " + r.activityInfo.name
+ r.activityInfo.name
+ " (from package " + " (from package "
+ r.activityInfo.applicationInfo.packageName + r.activityInfo.applicationInfo.packageName + ")");
+ ")");
} }
mMainApps.add(new ComponentName( mMainApps.add(new ComponentName(r.activityInfo.applicationInfo.packageName,
r.activityInfo.applicationInfo.packageName,
r.activityInfo.name)); r.activityInfo.name));
} else { } else {
if (mVerbose >= 3) { // very very verbose if (mVerbose >= 3) { // very very verbose
System.out.println("// - NOT USING main activity " System.out.println("// - NOT USING main activity "
+ r.activityInfo.name + r.activityInfo.name + " (from package "
+ " (from package " + r.activityInfo.applicationInfo.packageName + ")");
+ r.activityInfo.applicationInfo.packageName
+ ")");
} }
} }
} }
@@ -710,17 +730,20 @@ public class Monkey {
/** /**
* Run mCount cycles and see if we hit any crashers. * Run mCount cycles and see if we hit any crashers.
* * <p>
* TODO: Meta state on keys * TODO: Meta state on keys
* *
* @return Returns the last cycle which executed. If the value == mCount, no errors detected. * @return Returns the last cycle which executed. If the value == mCount, no
* errors detected.
*/ */
private int runMonkeyCycles() { private int runMonkeyCycles() {
int i = 0;
int eventCounter = 0;
int cycleCounter = 0;
boolean systemCrashed = false; boolean systemCrashed = false;
while (!systemCrashed && i < mCount) { while (!systemCrashed && cycleCounter < mCount) {
synchronized (this) { synchronized (this) {
if (mRequestAnrTraces) { if (mRequestAnrTraces) {
reportAnrTraces(); reportAnrTraces();
@@ -731,37 +754,36 @@ public class Monkey {
mRequestDumpsysMemInfo = false; mRequestDumpsysMemInfo = false;
} }
if (mMonitorNativeCrashes) { if (mMonitorNativeCrashes) {
// first time through, when i == 0, just set up the watcher (ignore the error) // first time through, when eventCounter == 0, just set up
if (checkNativeCrashes() && (i > 0)) { // the watcher (ignore the error)
if (checkNativeCrashes() && (eventCounter > 0)) {
System.out.println("** New native crash detected."); System.out.println("** New native crash detected.");
mAbort = mAbort || mKillProcessAfterError; mAbort = mAbort || mKillProcessAfterError;
} }
} }
if (mAbort) { if (mAbort) {
System.out.println("** Monkey aborted due to error."); System.out.println("** Monkey aborted due to error.");
System.out.println("Events injected: " + i); System.out.println("Events injected: " + eventCounter);
return i; return eventCounter;
} }
} }
// In this debugging mode, we never send any events. This is primarily // In this debugging mode, we never send any events. This is
// here so you can manually test the package or category limits, while manually // primarily here so you can manually test the package or category
// exercising the system. // limits, while manually exercising the system.
if (mSendNoEvents) { if (mSendNoEvents) {
i++; eventCounter++;
cycleCounter++;
continue; continue;
} }
if ((mVerbose > 0) && (i % 100) == 0 && i != 0) { if ((mVerbose > 0) && (eventCounter % 100) == 0 && eventCounter != 0) {
System.out.println(" // Sending event #" + i); System.out.println(" // Sending event #" + eventCounter);
} }
MonkeyEvent ev = mEventSource.getNextEvent(); MonkeyEvent ev = mEventSource.getNextEvent();
if (ev != null) { 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); int injectCode = ev.injectEvent(mWm, mAm, mVerbose);
if (injectCode == MonkeyEvent.INJECT_FAIL) { if (injectCode == MonkeyEvent.INJECT_FAIL) {
if (ev instanceof MonkeyKeyEvent) { if (ev instanceof MonkeyKeyEvent) {
@@ -780,19 +802,32 @@ public class Monkey {
System.err.println("** Error: SecurityException while injecting event."); System.err.println("** Error: SecurityException while injecting event.");
} }
} }
// Don't count throttling as an event.
if (!(ev instanceof MonkeyThrottleEvent)) {
eventCounter++;
if (mCountEvents) {
cycleCounter++;
}
}
} else { } else {
// Event Source has signaled that we have no more events to process if (!mCountEvents) {
System.err.println("** Error: Event source exhausted."); cycleCounter++;
break; } else {
System.err.println("** Error: Event source exhausted.");
break;
}
} }
} }
System.out.println("Events injected: " + i);
return i; // If we got this far, we succeeded!
System.out.println("Events injected: " + eventCounter);
return eventCounter;
} }
/** /**
* Send SIGNAL_USR1 to all processes. This will generate large (5mb) profiling reports * Send SIGNAL_USR1 to all processes. This will generate large (5mb)
* in data/misc, so use with care. * profiling reports in data/misc, so use with care.
*/ */
private void signalPersistentProcesses() { private void signalPersistentProcesses() {
try { try {
@@ -808,14 +843,16 @@ public class Monkey {
} }
/** /**
* Watch for appearance of new tombstone files, which indicate native crashes. * Watch for appearance of new tombstone files, which indicate native
* crashes.
* *
* @return Returns true if new files have appeared in the list * @return Returns true if new files have appeared in the list
*/ */
private boolean checkNativeCrashes() { private boolean checkNativeCrashes() {
String[] tombstones = TOMBSTONES_PATH.list(); String[] tombstones = TOMBSTONES_PATH.list();
// shortcut path for usually empty directory, so we don't waste even more objects // shortcut path for usually empty directory, so we don't waste even
// more objects
if ((tombstones == null) || (tombstones.length == 0)) { if ((tombstones == null) || (tombstones.length == 0)) {
mTombstones = null; mTombstones = null;
return false; return false;
@@ -836,17 +873,20 @@ public class Monkey {
} }
/** /**
* Return the next command line option. This has a number of special cases which * Return the next command line option. This has a number of special cases
* closely, but not exactly, follow the POSIX command line options patterns: * which closely, but not exactly, follow the POSIX command line options
* patterns:
* *
* <pre>
* -- means to stop processing additional options * -- means to stop processing additional options
* -z means option z * -z means option z
* -z ARGS means option z with (non-optional) arguments ARGS * -z ARGS means option z with (non-optional) arguments ARGS
* -zARGS means option z with (optional) arguments ARGS * -zARGS means option z with (optional) arguments ARGS
* --zz means option zz * --zz means option zz
* --zz ARGS means option zz with (non-optional) arguments ARGS * --zz ARGS means option zz with (non-optional) arguments ARGS
* </pre>
* *
* Note that you cannot combine single letter options; -abc != -a -b -c * Note that you cannot combine single letter options; -abc != -a -b -c
* *
* @return Returns the option string, or null if there are no more options. * @return Returns the option string, or null if there are no more options.
*/ */
@@ -893,7 +933,8 @@ public class Monkey {
} }
/** /**
* Returns a long converted from the next data argument, with error handling if not available. * Returns a long converted from the next data argument, with error handling
* if not available.
* *
* @param opt The name of the option. * @param opt The name of the option.
* @return Returns a long converted from the argument. * @return Returns a long converted from the argument.
@@ -927,19 +968,21 @@ public class Monkey {
* Print how to use this command. * Print how to use this command.
*/ */
private void showUsage() { private void showUsage() {
System.err.println("usage: monkey [-p ALLOWED_PACKAGE [-p ALLOWED_PACKAGE] ...]"); StringBuffer usage = new StringBuffer();
System.err.println(" [-c MAIN_CATEGORY [-c MAIN_CATEGORY] ...]"); usage.append("usage: monkey [-p ALLOWED_PACKAGE [-p ALLOWED_PACKAGE] ...]\n");
System.err.println(" [--ignore-crashes] [--ignore-timeouts]"); usage.append(" [-c MAIN_CATEGORY [-c MAIN_CATEGORY] ...]\n");
System.err.println(" [--ignore-security-exceptions] [--monitor-native-crashes]"); usage.append(" [--ignore-crashes] [--ignore-timeouts]\n");
System.err.println(" [--kill-process-after-error] [--hprof]"); usage.append(" [--ignore-security-exceptions] [--monitor-native-crashes]\n");
System.err.println(" [--pct-touch PERCENT] [--pct-motion PERCENT]"); usage.append(" [--kill-process-after-error] [--hprof]\n");
System.err.println(" [--pct-trackball PERCENT] [--pct-syskeys PERCENT]"); usage.append(" [--pct-touch PERCENT] [--pct-motion PERCENT]\n");
System.err.println(" [--pct-nav PERCENT] [--pct-majornav PERCENT]"); usage.append(" [--pct-trackball PERCENT] [--pct-syskeys PERCENT]\n");
System.err.println(" [--pct-appswitch PERCENT] [--pct-flip PERCENT]"); usage.append(" [--pct-nav PERCENT] [--pct-majornav PERCENT]\n");
System.err.println(" [--pct-anyevent PERCENT]"); usage.append(" [--pct-appswitch PERCENT] [--pct-flip PERCENT]\n");
System.err.println(" [--wait-dbg] [--dbg-no-events] [-f scriptfile]"); usage.append(" [--pct-anyevent PERCENT]\n");
System.err.println(" [--port port]"); usage.append(" [--wait-dbg] [--dbg-no-events] [-f scriptfile]\n");
System.err.println(" [-s SEED] [-v [-v] ...] [--throttle MILLISEC]"); usage.append(" [--port port]\n");
System.err.println(" COUNT"); usage.append(" [-s SEED] [-v [-v] ...] [--throttle MILLISEC]\n");
} usage.append(" COUNT");
System.err.println(usage.toString());
}
} }

View File

@@ -19,23 +19,24 @@ package com.android.commands.monkey;
/** /**
* event source interface * event source interface
*/ */
public interface MonkeyEventSource { public interface MonkeyEventSource {
/** /**
*
* @return the next monkey event from the source * @return the next monkey event from the source
*/ */
public MonkeyEvent getNextEvent(); public MonkeyEvent getNextEvent();
/** /**
* set verbose to allow different level of log * set verbose to allow different level of log
*
* @param verbose output mode? 1= verbose, 2=very verbose * @param verbose output mode? 1= verbose, 2=very verbose
*/ */
public void setVerbose(int verbose); public void setVerbose(int verbose);
/** /**
* check whether precondition is satisfied * check whether precondition is satisfied
* @return false if something fails, e.g. factor failure in random source *
* or file can not open from script source etc * @return false if something fails, e.g. factor failure in random source or
* file can not open from script source etc
*/ */
public boolean validate(); public boolean validate();
} }

View File

@@ -475,7 +475,7 @@ public class MonkeySourceRandom implements MonkeyEventSource {
*/ */
public MonkeyEvent getNextEvent() { public MonkeyEvent getNextEvent() {
if (mQ.isEmpty()) { if (mQ.isEmpty()) {
generateEvents(); generateEvents();
} }
mEventCount++; mEventCount++;
MonkeyEvent e = mQ.getFirst(); MonkeyEvent e = mQ.getFirst();

View File

@@ -23,9 +23,9 @@ import android.view.KeyEvent;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.NoSuchElementException;
/** /**
* monkey event queue. It takes a script to produce events sample script format: * monkey event queue. It takes a script to produce events sample script format:
@@ -77,10 +77,6 @@ public class MonkeySourceScript implements MonkeyEventSource {
// maximum number of events that we read at one time // maximum number of events that we read at one time
private static final int MAX_ONE_TIME_READS = 100; private static final int MAX_ONE_TIME_READS = 100;
// number of additional events added to the script
// add HOME_KEY down and up events to make start UI consistent in each round
private static final int POLICY_ADDITIONAL_EVENT_COUNT = 0;
// event key word in the capture log // event key word in the capture log
private static final String EVENT_KEYWORD_POINTER = "DispatchPointer"; private static final String EVENT_KEYWORD_POINTER = "DispatchPointer";
@@ -111,81 +107,99 @@ public class MonkeySourceScript implements MonkeyEventSource {
DataInputStream mInputStream; DataInputStream mInputStream;
BufferedReader mBufferReader; BufferedReader mBufferedReader;
/**
* Creates a MonkeySourceScript instance.
*
* @param filename The filename of the script (on the device).
* @param throttle The amount of time in ms to sleep between events.
*/
public MonkeySourceScript(String filename, long throttle) { public MonkeySourceScript(String filename, long throttle) {
mScriptFileName = filename; mScriptFileName = filename;
mQ = new MonkeyEventQueue(throttle); mQ = new MonkeyEventQueue(throttle);
} }
/** /**
* @return the number of total events that will be generated in a round * Resets the globals used to timeshift events.
*/ */
public int getOneRoundEventCount() {
// plus one home key down and up event
return mEventCountInScript + POLICY_ADDITIONAL_EVENT_COUNT;
}
private void resetValue() { private void resetValue() {
mLastRecordedDownTimeKey = 0; mLastRecordedDownTimeKey = 0;
mLastRecordedDownTimeMotion = 0; mLastRecordedDownTimeMotion = 0;
mLastRecordedEventTime = -1;
mLastExportDownTimeKey = 0; mLastExportDownTimeKey = 0;
mLastExportDownTimeMotion = 0; mLastExportDownTimeMotion = 0;
mLastRecordedEventTime = -1;
mLastExportEventTime = -1; mLastExportEventTime = -1;
} }
private boolean readScriptHeader() { /**
mEventCountInScript = -1; * Reads the header of the script file.
mFileOpened = false; *
try { * @return True if the file header could be parsed, and false otherwise.
if (THIS_DEBUG) { * @throws IOException If there was an error reading the file.
System.out.println("reading script header"); */
} private boolean readHeader() throws IOException {
mFileOpened = true;
mFStream = new FileInputStream(mScriptFileName); mFStream = new FileInputStream(mScriptFileName);
mInputStream = new DataInputStream(mFStream); mInputStream = new DataInputStream(mFStream);
mBufferReader = new BufferedReader(new InputStreamReader(mInputStream)); mBufferedReader = new BufferedReader(new InputStreamReader(mInputStream));
String sLine;
while ((sLine = mBufferReader.readLine()) != null) {
sLine = sLine.trim();
if (sLine.indexOf(HEADER_COUNT) >= 0) { String line;
try {
mEventCountInScript = Integer.parseInt(sLine.substring(
HEADER_COUNT.length() + 1).trim());
} catch (NumberFormatException e) {
System.err.println(e);
}
} else if (sLine.indexOf(HEADER_SPEED) >= 0) {
try {
mSpeed = Double.parseDouble(sLine.substring(HEADER_SPEED.length() + 1)
.trim());
} catch (NumberFormatException e) { while ((line = mBufferedReader.readLine()) != null) {
System.err.println(e); line = line.trim();
}
} else if (sLine.indexOf(STARTING_DATA_LINE) >= 0) { if (line.indexOf(HEADER_COUNT) >= 0) {
// header ends until we read the start data mark try {
mFileOpened = true; String value = line.substring(HEADER_COUNT.length() + 1).trim();
if (THIS_DEBUG) { mEventCountInScript = Integer.parseInt(value);
System.out.println("read script header success"); } catch (NumberFormatException e) {
} System.err.println(e);
return true; return false;
} }
} else if (line.indexOf(HEADER_SPEED) >= 0) {
try {
String value = line.substring(HEADER_COUNT.length() + 1).trim();
mSpeed = Double.parseDouble(value);
} catch (NumberFormatException e) {
System.err.println(e);
return false;
}
} else if (line.indexOf(STARTING_DATA_LINE) >= 0) {
return true;
} }
} catch (FileNotFoundException e) {
System.err.println(e);
} catch (IOException e) {
System.err.println(e);
} }
if (THIS_DEBUG) {
System.out.println("Error in reading script header");
}
return false; return false;
} }
/**
* Reads a number of lines and passes the lines to be processed.
*
* @return The number of lines read.
* @throws IOException If there was an error reading the file.
*/
private int readLines() throws IOException {
String line;
for (int i = 0; i < MAX_ONE_TIME_READS; i++) {
line = mBufferedReader.readLine();
if (line == null) {
return i;
}
line.trim();
processLine(line);
}
return MAX_ONE_TIME_READS;
}
/**
* Creates an event and adds it to the event queue. If the parameters are
* not understood, they are ignored and no events are added.
*
* @param s The entire string from the script file.
* @param args An array of arguments extracted from the script file line.
*/
private void handleEvent(String s, String[] args) { private void handleEvent(String s, String[] args) {
// Handle key event // Handle key event
if (s.indexOf(EVENT_KEYWORD_KEY) >= 0 && args.length == 8) { if (s.indexOf(EVENT_KEYWORD_KEY) >= 0 && args.length == 8) {
@@ -291,82 +305,77 @@ public class MonkeySourceScript implements MonkeyEventSource {
} }
} }
private void processLine(String s) { /**
int index1 = s.indexOf('('); * Extracts an event and a list of arguments from a line. If the line does
int index2 = s.indexOf(')'); * not match the format required, it is ignored.
*
* @param line A string in the form {@code cmd(arg1,arg2,arg3)}.
*/
private void processLine(String line) {
int index1 = line.indexOf('(');
int index2 = line.indexOf(')');
if (index1 < 0 || index2 < 0) { if (index1 < 0 || index2 < 0) {
return; return;
} }
String[] args = s.substring(index1 + 1, index2).split(","); String[] args = line.substring(index1 + 1, index2).split(",");
handleEvent(s, args); for (int i = 0; i < args.length; i++) {
args[i] = args[i].trim();
}
handleEvent(line, args);
} }
private void closeFile() { /**
* Closes the script file.
*
* @throws IOException If there was an error closing the file.
*/
private void closeFile() throws IOException {
mFileOpened = false; mFileOpened = false;
if (THIS_DEBUG) {
System.out.println("closing script file");
}
try { try {
mFStream.close(); mFStream.close();
mInputStream.close(); mInputStream.close();
} catch (IOException e) { } catch (NullPointerException e) {
System.out.println(e); // File was never opened so it can't be closed.
} }
} }
/** /**
* read next batch of events from the provided script file * Read next batch of events from the script file into the event queue.
* Checks if the script is open and then reads the next MAX_ONE_TIME_READS
* events or reads until the end of the file. If no events are read, then
* the script is closed.
* *
* @return true if success * @throws IOException If there was an error reading the file.
*/ */
private boolean readNextBatch() { private void readNextBatch() throws IOException {
/* int linesRead = 0;
* The script should restore the original state when it run multiple
* times.
*/
String sLine = null;
int readCount = 0;
if (THIS_DEBUG) { if (THIS_DEBUG) {
System.out.println("readNextBatch(): reading next batch of events"); System.out.println("readNextBatch(): reading next batch of events");
} }
if (!mFileOpened) { if (!mFileOpened) {
if (!readScriptHeader()) {
closeFile();
return false;
}
resetValue(); resetValue();
readHeader();
} }
try { linesRead = readLines();
while (readCount++ < MAX_ONE_TIME_READS && (sLine = mBufferReader.readLine()) != null) {
sLine = sLine.trim();
processLine(sLine);
}
} catch (IOException e) {
System.err.println(e);
return false;
}
if (sLine == null) { if (linesRead == 0) {
// to the end of the file
if (THIS_DEBUG) {
System.out.println("readNextBatch(): to the end of file");
}
closeFile(); closeFile();
} }
return true;
} }
/** /**
* sleep for a period of given time, introducing latency among events * Sleep for a period of given time. Used to introduce latency between
* events.
* *
* @param time to sleep in millisecond * @param time The amount of time to sleep in ms
*/ */
private void needSleep(long time) { private void needSleep(long time) {
if (time < 1) { if (time < 1) {
@@ -379,14 +388,23 @@ public class MonkeySourceScript implements MonkeyEventSource {
} }
/** /**
* check whether we can successfully read the header of the script file * Checks if the file can be opened and if the header is valid.
*
* @return True if the file exists and the header is valid, false otherwise.
*/ */
public boolean validate() { public boolean validate() {
boolean b = readNextBatch(); boolean validHeader;
try {
validHeader = readHeader();
closeFile();
} catch (IOException e) {
return false;
}
if (mVerbose > 0) { if (mVerbose > 0) {
System.out.println("Replaying " + mEventCountInScript + " events with speed " + mSpeed); System.out.println("Replaying " + mEventCountInScript + " events with speed " + mSpeed);
} }
return b; return validHeader;
} }
public void setVerbose(int verbose) { public void setVerbose(int verbose) {
@@ -394,10 +412,10 @@ public class MonkeySourceScript implements MonkeyEventSource {
} }
/** /**
* adjust key downtime and eventtime according to both recorded values and * Adjust key downtime and eventtime according to both recorded values and
* current system time * current system time.
* *
* @param e KeyEvent * @param e A KeyEvent
*/ */
private void adjustKeyEventTime(MonkeyKeyEvent e) { private void adjustKeyEventTime(MonkeyKeyEvent e) {
if (e.getEventTime() < 0) { if (e.getEventTime() < 0) {
@@ -431,10 +449,10 @@ public class MonkeySourceScript implements MonkeyEventSource {
} }
/** /**
* adjust motion downtime and eventtime according to both recorded values * Adjust motion downtime and eventtime according to both recorded values
* and current system time * and current system time.
* *
* @param e KeyEvent * @param e A KeyEvent
*/ */
private void adjustMotionEventTime(MonkeyMotionEvent e) { private void adjustMotionEventTime(MonkeyMotionEvent e) {
if (e.getEventTime() < 0) { if (e.getEventTime() < 0) {
@@ -469,25 +487,40 @@ public class MonkeySourceScript implements MonkeyEventSource {
} }
/** /**
* if the queue is empty, we generate events first * Gets the next event to be injected from the script. If the event queue is
* empty, reads the next n events from the script into the queue, where n is
* the lesser of the number of remaining events and the value specified by
* MAX_ONE_TIME_READS. If the end of the file is reached, no events are
* added to the queue and null is returned.
* *
* @return the first event in the queue, if null, indicating the system * @return The first event in the event queue or null if the end of the file
* crashes * is reached or if an error is encountered reading the file.
*/ */
public MonkeyEvent getNextEvent() { public MonkeyEvent getNextEvent() {
long recordedEventTime = -1; long recordedEventTime = -1;
MonkeyEvent ev;
if (mQ.isEmpty()) { if (mQ.isEmpty()) {
readNextBatch(); try {
readNextBatch();
} catch (IOException e) {
return null;
}
} }
MonkeyEvent e = mQ.getFirst();
mQ.removeFirst(); try {
if (e.getEventType() == MonkeyEvent.EVENT_TYPE_KEY) { ev = mQ.getFirst();
adjustKeyEventTime((MonkeyKeyEvent) e); mQ.removeFirst();
} else if (e.getEventType() == MonkeyEvent.EVENT_TYPE_POINTER } catch (NoSuchElementException e) {
|| e.getEventType() == MonkeyEvent.EVENT_TYPE_TRACKBALL) { return null;
adjustMotionEventTime((MonkeyMotionEvent) e);
} }
return e;
if (ev.getEventType() == MonkeyEvent.EVENT_TYPE_KEY) {
adjustKeyEventTime((MonkeyKeyEvent) ev);
} else if (ev.getEventType() == MonkeyEvent.EVENT_TYPE_POINTER
|| ev.getEventType() == MonkeyEvent.EVENT_TYPE_TRACKBALL) {
adjustMotionEventTime((MonkeyMotionEvent) ev);
}
return ev;
} }
} }