Loading tests/BouncyBall/READMEdeleted 100644 → 0 +0 −20 Original line number Diff line number Diff line This is a simple graphics app which draws a ball bouncing around a screen. This app is intended for use in automated testing (for example, to make sure no frames are dropped while running). It can also be run manually, but there's a fundamental assumption that this is the only foreground app running on the device while testing. From the top of tree, in a shell that has been set up for building, this app can be built with: $ mmma -j frameworks/base/tests/BouncyBall And then installed with: $ adb install ${ANDROID_PRODUCT_OUT}/system/app/BouncyBallTest/BouncyBallTest.apk This can be launched via automation with: $ adb shell am start com.prefabulated.bouncyball/com.prefabulated.bouncyball.BouncyBallActivity tests/BouncyBall/README.md 0 → 100644 +68 −0 Original line number Diff line number Diff line # BouncyBall test app This is a simple graphics app which draws a ball bouncing around the screen. This app's primary use is in automated testing, to make sure no frames are dropped while running. The graphics tested here are quite simple. This app is not just intended to assure very basic graphics work, but that the system does not drop frames as CPUs/GPU get turned off and downclocked while in the steady state. See https://source.android.com/docs/core/tests/debug/eval_perf#touchlatency for more details. ## Manual usage basics This app can be used outside of automation to check and debug this behavior. This app fundamentally assumes that it is the only foreground app running on the device while testing. If that assumption is broken, this app will log to logcat, at the "E"rror level, noting an "ASSUMPTION FAILURE". This app will log, at the "E"rror level, each time a frame is dropped. On a properly set up device, it is expected that this app never drops a frame. ### Helpful "flags" to flip The source code (in `app/src/main/java/com/prefabulated/bouncyball/BouncyBallActivity.java`) has a few constants which can be changed to help with debugging and testing. The app needs to be recompiled after any of these have been changed. * `LOG_EVERY_FRAME` If changed to `true`, the app will log, at the "D"ebug level, every (non-dropped) frame. * `FORCE_DROPPED_FRAMES` If changed to `true`, the app will drop every 64th frame. This can be helpful for debugging automation pipelines and confirming app behavior. * `ASSUMPTION_FAILURE_FORCES_EXIT` If changed to `false`, if the app fails the assumption that it is always in foreground focus, then the app will keep running (even though we know the results will be wrong). ## Local build and install From the top of tree, in a shell that has been set up for building: ``` $ mmma -j frameworks/base/tests/BouncyBall $ adb install ${ANDROID_PRODUCT_OUT}/system/app/BouncyBallTest/BouncyBallTest.apk ``` ## Launch app from command line Assuming the app is installed on the device: ``` $ adb shell am start com.prefabulated.bouncyball/com.prefabulated.bouncyball.BouncyBallActivity ``` ## Using Perfetto for analysis While the app will self-report when it drops frames, indicating an issue, that's not very helpful for figuring out why. TODO(b/408044970): Document how automation uses Perfetto, and how it can be used here. tests/BouncyBall/app/src/main/java/com/prefabulated/bouncyball/BouncyBallActivity.java +41 −2 Original line number Diff line number Diff line Loading @@ -33,11 +33,19 @@ public class BouncyBallActivity extends AppCompatActivity { // Since logging (to logcat) takes system resources, we chose not to log // data every frame by default. private static final boolean LOG_EVERY_FRAME = false; // To help with debugging and verifying behavior when frames are dropped, // this will drop one in every 64 frames. private static final boolean FORCE_DROPPED_FRAMES = false; // If the app fails an assumption we have for it (primarily that it is // the only foreground app), then its results cannot be trusted. By // default, we choose to immediately exit in this situation. Note that // dropping a frame is not considered an assumption failure. private static final boolean ASSUMPTION_FAILURE_FORCES_EXIT = true; private static final String LOG_TAG = "BouncyBall"; private static final float DESIRED_FRAME_RATE = 60.0f; // Our focus isn't smoothness on startup; it's smoothness once we're Loading @@ -45,6 +53,8 @@ public class BouncyBallActivity extends AppCompatActivity { private static final int INITIAL_FRAMES_TO_IGNORE = 6; private int mDisplayId = -1; private boolean mHasFocus = false; private boolean mWarmedUp = false; private float mFrameRate; private long mFrameMaxDurationNanos; private int mNumFramesDropped = 0; Loading Loading @@ -77,14 +87,23 @@ public class BouncyBallActivity extends AppCompatActivity { @Override public void doFrame(long frameTimeNanos) { if (mFrameCount >= INITIAL_FRAMES_TO_IGNORE) { if (mFrameCount == INITIAL_FRAMES_TO_IGNORE) { mWarmedUp = true; if (!mHasFocus) { String msg = "App does not have focus after " + mFrameCount + " frames"; reportAssumptionFailure(msg); } } if (mWarmedUp) { long elapsedNanos = frameTimeNanos - mLastFrameTimeNanos; if (elapsedNanos > mFrameMaxDurationNanos) { mNumFramesDropped++; Log.e(LOG_TAG, "FRAME DROPPED (total " + mNumFramesDropped + "): Took " + nanosToMillis(elapsedNanos) + "ms"); } else if (LOG_EVERY_FRAME) { Log.d(LOG_TAG, "Frame took " + nanosToMillis(elapsedNanos) + "ms"); Log.d(LOG_TAG, "Frame " + mFrameCount + " took " + nanosToMillis(elapsedNanos) + "ms"); } } mLastFrameTimeNanos = frameTimeNanos; Loading Loading @@ -125,6 +144,18 @@ public class BouncyBallActivity extends AppCompatActivity { Trace.endSection(); } @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (mWarmedUp) { // After our initial frames, this app should always be in focus. String state = hasFocus ? "gain" : "loss"; reportAssumptionFailure("Unexpected " + state + " of focus"); } mHasFocus = hasFocus; } // If available at our current resolution, use 60Hz. If not, use the // lowest refresh rate above 60Hz which is available. Otherwise, throw // an exception which kills the app. Loading Loading @@ -195,4 +226,12 @@ public class BouncyBallActivity extends AppCompatActivity { private float nanosToMillis(long nanos) { return nanos / (1_000_000.0f); } private void reportAssumptionFailure(String msg) { Log.e(LOG_TAG, "ASSUMPTION FAILURE. " + msg); if (ASSUMPTION_FAILURE_FORCES_EXIT) { Log.e(LOG_TAG, "Exiting app due to assumption failure."); System.exit(1); } } } Loading
tests/BouncyBall/READMEdeleted 100644 → 0 +0 −20 Original line number Diff line number Diff line This is a simple graphics app which draws a ball bouncing around a screen. This app is intended for use in automated testing (for example, to make sure no frames are dropped while running). It can also be run manually, but there's a fundamental assumption that this is the only foreground app running on the device while testing. From the top of tree, in a shell that has been set up for building, this app can be built with: $ mmma -j frameworks/base/tests/BouncyBall And then installed with: $ adb install ${ANDROID_PRODUCT_OUT}/system/app/BouncyBallTest/BouncyBallTest.apk This can be launched via automation with: $ adb shell am start com.prefabulated.bouncyball/com.prefabulated.bouncyball.BouncyBallActivity
tests/BouncyBall/README.md 0 → 100644 +68 −0 Original line number Diff line number Diff line # BouncyBall test app This is a simple graphics app which draws a ball bouncing around the screen. This app's primary use is in automated testing, to make sure no frames are dropped while running. The graphics tested here are quite simple. This app is not just intended to assure very basic graphics work, but that the system does not drop frames as CPUs/GPU get turned off and downclocked while in the steady state. See https://source.android.com/docs/core/tests/debug/eval_perf#touchlatency for more details. ## Manual usage basics This app can be used outside of automation to check and debug this behavior. This app fundamentally assumes that it is the only foreground app running on the device while testing. If that assumption is broken, this app will log to logcat, at the "E"rror level, noting an "ASSUMPTION FAILURE". This app will log, at the "E"rror level, each time a frame is dropped. On a properly set up device, it is expected that this app never drops a frame. ### Helpful "flags" to flip The source code (in `app/src/main/java/com/prefabulated/bouncyball/BouncyBallActivity.java`) has a few constants which can be changed to help with debugging and testing. The app needs to be recompiled after any of these have been changed. * `LOG_EVERY_FRAME` If changed to `true`, the app will log, at the "D"ebug level, every (non-dropped) frame. * `FORCE_DROPPED_FRAMES` If changed to `true`, the app will drop every 64th frame. This can be helpful for debugging automation pipelines and confirming app behavior. * `ASSUMPTION_FAILURE_FORCES_EXIT` If changed to `false`, if the app fails the assumption that it is always in foreground focus, then the app will keep running (even though we know the results will be wrong). ## Local build and install From the top of tree, in a shell that has been set up for building: ``` $ mmma -j frameworks/base/tests/BouncyBall $ adb install ${ANDROID_PRODUCT_OUT}/system/app/BouncyBallTest/BouncyBallTest.apk ``` ## Launch app from command line Assuming the app is installed on the device: ``` $ adb shell am start com.prefabulated.bouncyball/com.prefabulated.bouncyball.BouncyBallActivity ``` ## Using Perfetto for analysis While the app will self-report when it drops frames, indicating an issue, that's not very helpful for figuring out why. TODO(b/408044970): Document how automation uses Perfetto, and how it can be used here.
tests/BouncyBall/app/src/main/java/com/prefabulated/bouncyball/BouncyBallActivity.java +41 −2 Original line number Diff line number Diff line Loading @@ -33,11 +33,19 @@ public class BouncyBallActivity extends AppCompatActivity { // Since logging (to logcat) takes system resources, we chose not to log // data every frame by default. private static final boolean LOG_EVERY_FRAME = false; // To help with debugging and verifying behavior when frames are dropped, // this will drop one in every 64 frames. private static final boolean FORCE_DROPPED_FRAMES = false; // If the app fails an assumption we have for it (primarily that it is // the only foreground app), then its results cannot be trusted. By // default, we choose to immediately exit in this situation. Note that // dropping a frame is not considered an assumption failure. private static final boolean ASSUMPTION_FAILURE_FORCES_EXIT = true; private static final String LOG_TAG = "BouncyBall"; private static final float DESIRED_FRAME_RATE = 60.0f; // Our focus isn't smoothness on startup; it's smoothness once we're Loading @@ -45,6 +53,8 @@ public class BouncyBallActivity extends AppCompatActivity { private static final int INITIAL_FRAMES_TO_IGNORE = 6; private int mDisplayId = -1; private boolean mHasFocus = false; private boolean mWarmedUp = false; private float mFrameRate; private long mFrameMaxDurationNanos; private int mNumFramesDropped = 0; Loading Loading @@ -77,14 +87,23 @@ public class BouncyBallActivity extends AppCompatActivity { @Override public void doFrame(long frameTimeNanos) { if (mFrameCount >= INITIAL_FRAMES_TO_IGNORE) { if (mFrameCount == INITIAL_FRAMES_TO_IGNORE) { mWarmedUp = true; if (!mHasFocus) { String msg = "App does not have focus after " + mFrameCount + " frames"; reportAssumptionFailure(msg); } } if (mWarmedUp) { long elapsedNanos = frameTimeNanos - mLastFrameTimeNanos; if (elapsedNanos > mFrameMaxDurationNanos) { mNumFramesDropped++; Log.e(LOG_TAG, "FRAME DROPPED (total " + mNumFramesDropped + "): Took " + nanosToMillis(elapsedNanos) + "ms"); } else if (LOG_EVERY_FRAME) { Log.d(LOG_TAG, "Frame took " + nanosToMillis(elapsedNanos) + "ms"); Log.d(LOG_TAG, "Frame " + mFrameCount + " took " + nanosToMillis(elapsedNanos) + "ms"); } } mLastFrameTimeNanos = frameTimeNanos; Loading Loading @@ -125,6 +144,18 @@ public class BouncyBallActivity extends AppCompatActivity { Trace.endSection(); } @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (mWarmedUp) { // After our initial frames, this app should always be in focus. String state = hasFocus ? "gain" : "loss"; reportAssumptionFailure("Unexpected " + state + " of focus"); } mHasFocus = hasFocus; } // If available at our current resolution, use 60Hz. If not, use the // lowest refresh rate above 60Hz which is available. Otherwise, throw // an exception which kills the app. Loading Loading @@ -195,4 +226,12 @@ public class BouncyBallActivity extends AppCompatActivity { private float nanosToMillis(long nanos) { return nanos / (1_000_000.0f); } private void reportAssumptionFailure(String msg) { Log.e(LOG_TAG, "ASSUMPTION FAILURE. " + msg); if (ASSUMPTION_FAILURE_FORCES_EXIT) { Log.e(LOG_TAG, "Exiting app due to assumption failure."); System.exit(1); } } }