This test tests the function of iorap from perfetto collection -> compilation -> - * prefetching. - *
+ *This test tests the function of iorap from: + * perfetto collection -> compilation -> prefetching -> version update -> perfetto collection. */ @RunWith(AndroidJUnit4.class) public class IorapWorkFlowTest { - private static final String TAG = "IorapWorkFlowTest"; - private static final String TEST_PACKAGE_NAME = "com.android.settings"; - private static final String TEST_ACTIVITY_NAME = "com.android.settings.Settings"; + private static final String TEST_APP_VERSION_ONE_PATH = "/data/misc/iorapd/iorap_test_app_v1.apk"; + private static final String TEST_APP_VERSION_TWO_PATH = "/data/misc/iorapd/iorap_test_app_v2.apk"; + private static final String TEST_APP_VERSION_THREE_PATH = "/data/misc/iorapd/iorap_test_app_v3.apk"; private static final String DB_PATH = "/data/misc/iorapd/sqlite.db"; private static final Duration TIMEOUT = Duration.ofSeconds(300L); - private static final String READAHEAD_INDICATOR = - "Description = /data/misc/iorapd/com.android.settings/-?\\d+/com.android.settings.Settings/compiled_traces/compiled_trace.pb"; - private UiDevice mDevice; @Before - public void startMainActivityFromHomeScreen() throws Exception { + public void setUp() throws Exception { // Initialize UiDevice instance mDevice = UiDevice.getInstance(getInstrumentation()); @@ -88,21 +89,81 @@ public class IorapWorkFlowTest { mDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), TIMEOUT.getSeconds()); } + @After + public void tearDown() throws Exception { + String packageName = "com.example.ioraptestapp"; + uninstallApk(packageName); + } + @Test (timeout = 300000) - public void testApp() throws Exception { + public void testNormalWorkFlow() throws Exception { assertThat(mDevice, notNullValue()); + // Install test app version one + installApk(TEST_APP_VERSION_ONE_PATH); + String packageName = "com.example.ioraptestapp"; + String activityName = "com.example.ioraptestapp.MainActivity"; + // Perfetto trace collection phase. - assertTrue(startAppForPerfettoTrace(/*expectPerfettoTraceCount=*/1)); - assertTrue(startAppForPerfettoTrace(/*expectPerfettoTraceCount=*/2)); - assertTrue(startAppForPerfettoTrace(/*expectPerfettoTraceCount=*/3)); - assertTrue(checkPerfettoTracesExistence(TIMEOUT, 3)); + assertTrue(startAppForPerfettoTrace( + packageName, activityName, /*version=*/1L)); + assertTrue(startAppForPerfettoTrace( + packageName, activityName, /*version=*/1L)); + assertTrue(startAppForPerfettoTrace( + packageName, activityName, /*version=*/1L)); // Trigger maintenance service for compilation. - assertTrue(compile(TIMEOUT)); + TimeUnit.SECONDS.sleep(5L); + assertTrue(compile(packageName, activityName, /*version=*/1L)); - // Check if prefetching works. - assertTrue(waitForPrefetchingFromLogcat(/*expectPerfettoTraceCount=*/3)); + // Run app with prefetching + assertTrue(startAppWithCompiledTrace( + packageName, activityName, /*version=*/1L)); + } + + @Test (timeout = 300000) + public void testUpdateApp() throws Exception { + assertThat(mDevice, notNullValue()); + + // Install test app version two, + String packageName = "com.example.ioraptestapp"; + String activityName = "com.example.ioraptestapp.MainActivity"; + installApk(TEST_APP_VERSION_TWO_PATH); + + // Perfetto trace collection phase. + assertTrue(startAppForPerfettoTrace( + packageName, activityName, /*version=*/2L)); + assertTrue(startAppForPerfettoTrace( + packageName, activityName, /*version=*/2L)); + assertTrue(startAppForPerfettoTrace( + packageName, activityName, /*version=*/2L)); + + // Trigger maintenance service for compilation. + TimeUnit.SECONDS.sleep(5L); + assertTrue(compile(packageName, activityName, /*version=*/2L)); + + // Run app with prefetching + assertTrue(startAppWithCompiledTrace( + packageName, activityName, /*version=*/2L)); + + // Update test app to version 3 + installApk(TEST_APP_VERSION_THREE_PATH); + + // Rerun app, should do pefetto tracing. + assertTrue(startAppForPerfettoTrace( + packageName, activityName, /*version=*/3L)); + } + + private static void installApk(String apkPath) throws Exception { + // Disable the selinux to allow pm install apk in the dir. + executeShellCommand("setenforce 0"); + executeShellCommand("pm install -r -d " + apkPath); + executeShellCommand("setenforce 1"); + + } + + private static void uninstallApk(String apkPath) throws Exception { + executeShellCommand("pm uninstall " + apkPath); } /** @@ -110,43 +171,81 @@ public class IorapWorkFlowTest { * * @param expectPerfettoTraceCount is the expected count of perfetto traces. */ - private boolean startAppForPerfettoTrace(long expectPerfettoTraceCount) + private boolean startAppForPerfettoTrace( + String packageName, String activityName, long version) throws Exception { - // Close the specified app if it's open - closeApp(); - // Launch the specified app - startApp(); - // Wait for the app to appear - mDevice.wait(Until.hasObject(By.pkg(TEST_PACKAGE_NAME).depth(0)), TIMEOUT.getSeconds()); + LogcatTimestamp timestamp = runAppOnce(packageName, activityName); + return waitForPerfettoTraceSavedFromLogcat( + packageName, activityName, version, timestamp); + } - String sql = "SELECT COUNT(*) FROM activities " - + "JOIN app_launch_histories ON activities.id = app_launch_histories.activity_id " - + "JOIN raw_traces ON raw_traces.history_id = app_launch_histories.id " - + "WHERE activities.name = ?"; - return checkAndWaitEntriesNum(sql, new String[]{TEST_ACTIVITY_NAME}, expectPerfettoTraceCount, - TIMEOUT); + private boolean startAppWithCompiledTrace( + String packageName, String activityName, long version) + throws Exception { + LogcatTimestamp timestamp = runAppOnce(packageName, activityName); + return waitForPrefetchingFromLogcat( + packageName, activityName, version, timestamp); + } + + private LogcatTimestamp runAppOnce(String packageName, String activityName) throws Exception { + // Close the specified app if it's open + closeApp(packageName); + LogcatTimestamp timestamp = new LogcatTimestamp(); + // Launch the specified app + startApp(packageName, activityName); + // Wait for the app to appear + mDevice.wait(Until.hasObject(By.pkg(packageName).depth(0)), TIMEOUT.getSeconds()); + return timestamp; } // Invokes the maintenance to compile the perfetto traces to compiled trace. - private boolean compile(Duration timeout) throws Exception { + private boolean compile( + String packageName, String activityName, long version) throws Exception { // The job id (283673059) is defined in class IorapForwardingService. - executeShellCommand("cmd jobscheduler run -f android 283673059"); + executeShellCommandViaTmpFile("cmd jobscheduler run -f android 283673059"); + return waitForFileExistence(getCompiledTracePath(packageName, activityName, version)); + } - // Wait for the compilation. - String sql = "SELECT COUNT(*) FROM activities JOIN prefetch_files ON " - + "activities.id = prefetch_files.activity_id " - + "WHERE activities.name = ?"; - boolean result = checkAndWaitEntriesNum(sql, new String[]{TEST_ACTIVITY_NAME}, /*count=*/1, - timeout); - if (!result) { - return false; + private String getCompiledTracePath( + String packageName, String activityName, long version) { + return String.format( + "/data/misc/iorapd/%s/%d/%s/compiled_traces/compiled_trace.pb", + packageName, version, activityName); + } + + /** + * Starts the testing app. + */ + private void startApp(String packageName, String activityName) throws Exception { + executeShellCommandViaTmpFile( + String.format("am start %s/%s", packageName, activityName)); + } + + /** + * Closes the testing app. + *
Keep trying to kill the process of the app until no process of the app package + * appears.
+ */ + private void closeApp(String packageName) throws Exception { + while (true) { + String pid = executeShellCommand("pidof " + packageName); + if (pid.isEmpty()) { + Log.i(TAG, "Closed app " + packageName); + return; + } + executeShellCommand("kill -9 " + pid); + TimeUnit.SECONDS.sleep(1L); } + } - return retryWithTimeout(timeout, () -> { + /** Waits for a file to appear. */ + private boolean waitForFileExistence(String fileName) throws Exception { + return retryWithTimeout(TIMEOUT, () -> { try { - String compiledTrace = getCompiledTraceFilePath(); - File compiledTraceLocal = copyFileToLocal(compiledTrace, "compiled_trace.tmp"); - return compiledTraceLocal.exists(); + String fileExists = executeShellCommandViaTmpFile( + String.format("test -f %s; echo $?", fileName)); + Log.i(TAG, fileName + " existence is " + fileExists); + return fileExists.trim().equals("0"); } catch (Exception e) { Log.i(TAG, e.getMessage()); return false; @@ -154,92 +253,96 @@ public class IorapWorkFlowTest { }); } - /** - * Check if all the perfetto traces in the db exist. - */ - private boolean checkPerfettoTracesExistence(Duration timeout, int expectPerfettoTraceCount) + /** Waits for the perfetto trace saved message from logcat. */ + private boolean waitForPerfettoTraceSavedFromLogcat( + String packageName, String activityName, long version, LogcatTimestamp timestamp) throws Exception { - return retryWithTimeout(timeout, () -> { - try { - File dbFile = getIorapDb(); - ListKeep checking until the timeout. + *
When prefetching works, the perfetto traces should not be collected.
*/ - private boolean checkAndWaitEntriesNum(String sql, String[] selectionArgs, long count, - Duration timeout) + private boolean waitForPrefetchingFromLogcat( + String packageName, String activityName, long version, LogcatTimestamp timestamp) throws Exception { - return retryWithTimeout(timeout, () -> { + Pattern p = Pattern.compile( + ".*" + getReadaheadIndicator(packageName, activityName, version) + + ".*Total File Paths=(\\d+) \\(good: (\\d+[.]?\\d*)%\\)\n" + + ".*Total Entries=(\\d+) \\(good: (\\d+[.]?\\d*)%\\)\n" + + ".*Total Bytes=(\\d+) \\(good: (\\d+[.]?\\d*)%\\).*", + Pattern.DOTALL); + + return retryWithTimeout(TIMEOUT, () -> { try { - File db = getIorapDb(); - long curCount = getEntriesNum(db, selectionArgs, sql); - Log.i(TAG, String - .format("For %s, current count is %d, expected count is :%d.", sql, curCount, - count)); - return curCount == count; - } catch (Exception e) { - Log.i(TAG, e.getMessage()); + String log = timestamp.getLogcatAfter(); + Matcher m = p.matcher(log); + if (!m.matches()) { + Log.i(TAG, "Cannot find readahead log."); + return false; + } + + int totalFilePath = Integer.parseInt(m.group(1)); + float totalFilePathGoodRate = Float.parseFloat(m.group(2)) / 100; + int totalEntries = Integer.parseInt(m.group(3)); + float totalEntriesGoodRate = Float.parseFloat(m.group(4)) / 100; + int totalBytes = Integer.parseInt(m.group(5)); + float totalBytesGoodRate = Float.parseFloat(m.group(6)) / 100; + + Log.i(TAG, String.format( + "totalFilePath: %d (good %.2f) totalEntries: %d (good %.2f) totalBytes: %d (good %.2f)", + totalFilePath, totalFilePathGoodRate, totalEntries, totalEntriesGoodRate, totalBytes, + totalBytesGoodRate)); + + return totalFilePath > 0 && + totalEntries > 0 && + totalBytes > 0 && + totalFilePathGoodRate > 0.5 && + totalEntriesGoodRate > 0.5 && + totalBytesGoodRate > 0.5; + } catch(Exception e) { return false; } - }); + }); } - /** - * Retry until timeout. - */ + private static String getReadaheadIndicator( + String packageName, String activityName, long version) { + return String.format( + "Description = /data/misc/iorapd/%s/%d/%s/compiled_traces/compiled_trace.pb", + packageName, version, activityName); + } + + /** Retry until timeout. */ private boolean retryWithTimeout(Duration timeout, BooleanSupplier supplier) throws Exception { long totalSleepTimeSeconds = 0L; long sleepIntervalSeconds = 2L; @@ -256,112 +359,27 @@ public class IorapWorkFlowTest { } /** - * Gets the number of entries in the query of sql. - */ - private long getEntriesNum(File dbFile, String[] selectionArgs, String sql) throws Exception { - try (SQLiteDatabase db = SQLiteDatabase - .openDatabase(dbFile.getPath(), null, SQLiteDatabase.OPEN_READONLY)) { - return DatabaseUtils.longForQuery(db, sql, selectionArgs); - } - } - - /** - * Gets the iorapd sqlite db file. + * Executes command in adb shell via a tmp file. * - *The test cannot access the db file directly under "/data/misc/iorapd". - * Copy it to the local directory and change the mode. + *
This should be run as root.
*/ - private File getIorapDb() throws Exception { - File tmpDb = copyFileToLocal("/data/misc/iorapd/sqlite.db", "tmp.db"); - // Change the mode of the file to allow the access from test. - executeShellCommand("chmod 777 " + tmpDb.getPath()); - return tmpDb; - } - - /** - * Copys a file to local directory. - */ - private File copyFileToLocal(String src, String tgtFileName) throws Exception { - File localDir = getApplicationContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE); - File localFile = new File(localDir, tgtFileName); - executeShellCommand(String.format("cp %s %s", src, localFile.getPath())); - return localFile; - } - - /** - * Starts the testing app. - */ - private void startApp() throws Exception { - Context context = getApplicationContext(); - final Intent intent = context.getPackageManager() - .getLaunchIntentForPackage(TEST_PACKAGE_NAME); - context.startActivity(intent); - Log.i(TAG, "Started app " + TEST_PACKAGE_NAME); - } - - /** - * Closes the testing app. - *Keep trying to kill the process of the app until no process of the app package - * appears.
- */ - private void closeApp() throws Exception { - while (true) { - String pid = executeShellCommand("pidof " + TEST_PACKAGE_NAME); - if (pid.isEmpty()) { - Log.i(TAG, "Closed app " + TEST_PACKAGE_NAME); - return; + private static String executeShellCommandViaTmpFile(String cmd) throws Exception { + Log.i(TAG, "Execute via tmp file: " + cmd); + Path tmp = null; + try { + tmp = Files.createTempFile(/*prefix=*/null, /*suffix=*/".sh"); + Files.write(tmp, cmd.getBytes(StandardCharsets.UTF_8)); + tmp.toFile().setExecutable(true); + return UiDevice.getInstance( + InstrumentationRegistry.getInstrumentation()). + executeShellCommand(tmp.toString()); + } finally { + if (tmp != null) { + Files.delete(tmp); } - executeShellCommand("kill -9 " + pid); - TimeUnit.SECONDS.sleep(1L); } } - /** - * Waits for the prefetching log in the logcat. - * - *When prefetching works, the perfetto traces should not be collected.
- */ - private boolean waitForPrefetchingFromLogcat(long expectPerfettoTraceCount) throws Exception { - if (!startAppForPerfettoTrace(expectPerfettoTraceCount)) { - return false; - } - - String log = executeShellCommand("logcat -d"); - - Pattern p = Pattern.compile( - ".*" + READAHEAD_INDICATOR - + ".*Total File Paths=(\\d+) \\(good: (\\d+[.]?\\d*)%\\)\n" - + ".*Total Entries=(\\d+) \\(good: (\\d+[.]?\\d*)%\\)\n" - + ".*Total Bytes=(\\d+) \\(good: (\\d+[.]?\\d*)%\\).*", - Pattern.DOTALL); - Matcher m = p.matcher(log); - - if (!m.matches()) { - Log.i(TAG, "Cannot find readahead log."); - return false; - } - - int totalFilePath = Integer.parseInt(m.group(1)); - float totalFilePathGoodRate = Float.parseFloat(m.group(2)) / 100; - int totalEntries = Integer.parseInt(m.group(3)); - float totalEntriesGoodRate = Float.parseFloat(m.group(4)) / 100; - int totalBytes = Integer.parseInt(m.group(5)); - float totalBytesGoodRate = Float.parseFloat(m.group(6)) / 100; - - Log.i(TAG, String.format( - "totalFilePath: %d (good %.2f) totalEntries: %d (good %.2f) totalBytes: %d (good %.2f)", - totalFilePath, totalFilePathGoodRate, totalEntries, totalEntriesGoodRate, totalBytes, - totalBytesGoodRate)); - - return totalFilePath > 0 && - totalEntries > 0 && - totalBytes > 100000 && - totalFilePathGoodRate > 0.5 && - totalEntriesGoodRate > 0.5 && - totalBytesGoodRate > 0.5; - } - - /** * Executes command in adb shell. * @@ -372,6 +390,27 @@ public class IorapWorkFlowTest { return UiDevice.getInstance( InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd); } + + static class LogcatTimestamp { + private String epochTime; + + public LogcatTimestamp() throws Exception{ + long currentTimeMillis = System.currentTimeMillis(); + epochTime = String.format( + "%d.%d", currentTimeMillis/1000, currentTimeMillis%1000); + Log.i(TAG, "Current logcat timestamp is " + epochTime); + } + + // For example, 1585264100.000 + public String getEpochTime() { + return epochTime; + } + + // Gets the logcat after this epoch time. + public String getLogcatAfter() throws Exception { + return executeShellCommandViaTmpFile( + "logcat -v epoch -t '" + epochTime + "'"); + } + } } - diff --git a/startop/iorap/functional_tests/test_data/iorap_test_app_v1.apk b/startop/iorap/functional_tests/test_data/iorap_test_app_v1.apk new file mode 120000 index 0000000000000..1c1a437f6a557 --- /dev/null +++ b/startop/iorap/functional_tests/test_data/iorap_test_app_v1.apk @@ -0,0 +1 @@ +../../../../../../packages/modules/ArtPrebuilt/iorap/test/iorap_test_app_v1.apk \ No newline at end of file diff --git a/startop/iorap/functional_tests/test_data/iorap_test_app_v2.apk b/startop/iorap/functional_tests/test_data/iorap_test_app_v2.apk new file mode 120000 index 0000000000000..7cd41c48ba3a7 --- /dev/null +++ b/startop/iorap/functional_tests/test_data/iorap_test_app_v2.apk @@ -0,0 +1 @@ +../../../../../../packages/modules/ArtPrebuilt/iorap/test/iorap_test_app_v2.apk \ No newline at end of file diff --git a/startop/iorap/functional_tests/test_data/iorap_test_app_v3.apk b/startop/iorap/functional_tests/test_data/iorap_test_app_v3.apk new file mode 120000 index 0000000000000..7f4e996e57d0c --- /dev/null +++ b/startop/iorap/functional_tests/test_data/iorap_test_app_v3.apk @@ -0,0 +1 @@ +../../../../../../packages/modules/ArtPrebuilt/iorap/test/iorap_test_app_v3.apk \ No newline at end of file