diff options
author | SzuWei Lin <szuweilin@google.com> | 2020-09-01 18:47:59 +0800 |
---|---|---|
committer | SzuWei Lin <szuweilin@google.com> | 2020-09-01 18:47:59 +0800 |
commit | 8b1aefc99131638f1dd1d0259f9de1663918935d (patch) | |
tree | 24b7192ab08575b8753cbc1e433552377021c19f | |
parent | bed0db716e3ba7d27b3f15ae7f4673a290c52dba (diff) | |
parent | 66327d18426083a910176ef02d9a386c33983f14 (diff) | |
download | platform_testing-8b1aefc99131638f1dd1d0259f9de1663918935d.tar.gz |
Merge branch android10-qpr3-releaseandroid10-gsi
Change-Id: Iec1447649335e2b4f12ce5a0e28b733e67c4ed82
47 files changed, 2111 insertions, 71 deletions
diff --git a/build/tasks/tests/platform_test_list.mk b/build/tasks/tests/platform_test_list.mk index e7a5a18df..d1e484fa8 100644 --- a/build/tasks/tests/platform_test_list.mk +++ b/build/tasks/tests/platform_test_list.mk @@ -56,6 +56,7 @@ platform_tests += \ FrameworksUtilTests \ InternalLocTestApp \ JankMicroBenchmarkTests \ + long_trace_config.textproto \ LauncherRotationStressTest \ MemoryUsage \ MultiDexLegacyTestApp \ @@ -92,6 +93,9 @@ platform_tests += \ SmokeTestApp \ SysAppJankTestsWear \ TouchLatencyJankTestWear \ + trace_config.textproto \ + trace_config_detailed.textproto \ + trace_config_experimental.textproto \ UbSystemUiJankTests \ UbWebViewJankTests \ UiBench \ diff --git a/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoGenericAppHelper.java b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoGenericAppHelper.java new file mode 100644 index 000000000..060004ba5 --- /dev/null +++ b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoGenericAppHelper.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.platform.helpers; + +import java.util.Map; + +/** + * An interface to open any applications. One of the member methods must be invoked after creating + * an instance of this class. + */ +public interface IAutoGenericAppHelper extends IAppHelper, Scrollable { + /** + * Set the package to open. The application will be opened using the info activity or launcher + * activity of the package that has been injected here. + */ + void setPackage(String pkg); + + /** + * Set the launch activity. The application will be opened directly using the provided activity. + */ + void setLaunchActivity(String pkg); + + /** + * Set the launch action. The application will be opened using the provided launch action with + * given extra arguments. + */ + void setLaunchAction(String action, Map<String, String> extraArgs); +} diff --git a/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoGooglePlayHelper.java b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoGooglePlayHelper.java index c80360790..d3330db57 100644 --- a/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoGooglePlayHelper.java +++ b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoGooglePlayHelper.java @@ -35,28 +35,29 @@ public interface IAutoGooglePlayHelper extends IAppHelper, Scrollable { /** * Setup expectations: Google Play app is open. * - * This method is used to install a app. + * <p>This method is used to install a app. */ void installApp(); /** * Setup expectations: Google Play app is open. * - * This method is used to cancel a download. + * <p>This method is used to cancel a download. */ void cancelDownload(); /** * Setup expectations: Google Play app is open. * - * This method is used to open a installed app. + * <p>This method is used to return back to Google Play main page */ - void openApp(); + void returnToMainPage(); /** * Setup expectations: Google Play app is open. * - * This method is used to return back to Google Play main page + * <p>This method is used to open a installed app. */ - void returnToMainPage(); + @Deprecated + void openApp(); } diff --git a/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoMediaCenterHelper.java b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoMediaCenterHelper.java new file mode 100644 index 000000000..52caa07e5 --- /dev/null +++ b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoMediaCenterHelper.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.platform.helpers; + +public interface IAutoMediaCenterHelper extends IAppHelper { + + /** + * Setup expectations: Media test app is open. + * + * This method is used to set account type. + */ + void setAccountType(String accountType); + + /** + * Setup expectations: Media test app is open. + * + * This method is used to set root node type. + */ + void setRootNodeType(String rootNodeType); + + /** + * Setup expectations: Media test app is open. + * + * This method is used to set root reply delay. + */ + void setRootReplyDelay(String rootReplyDelay); + + /** + * Setup expectations: media test app is open. + * + * This method is used to open Folder Menu with menuOptions. + * Example - openMenu->Folder->Mediafilename->trackName + * openMenuWith(Folder,mediafilename,trackName); + * + * @param - menuOptions used to pass multiple level of menu options in one go. + */ + void selectMediaTrack(String... menuOptions); + + /** + * Setup expectations: media test app is open. + * + * This method is used to open settings. + */ + void openSettings(); + + /** + * Setup expectations: media test app is open. + * + * This method is used to close settings. + */ + void closeSettings(); + + /** + * Setup expectations: media test app is open. + * + * @param - tabName used to select tab name. + */ + void selectTab(String tabName); + + /** + * Setup expectations: media test app is open. + * + * @param - title used search media track,artist. + */ + void search(String title); + + /** + * This method is used to check if media is currently playing Returns true if media is playing + * else returns false + */ + boolean isPlaying(); +} diff --git a/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoMediaCenterMinimizeControlBarHelper.java b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoMediaCenterMinimizeControlBarHelper.java new file mode 100644 index 000000000..f90c0ae88 --- /dev/null +++ b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoMediaCenterMinimizeControlBarHelper.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.platform.helpers; + +public interface IAutoMediaCenterMinimizeControlBarHelper extends IAppHelper { + + /** + * Setup expectations: media test app is open and Minimize control bar present. + * + * This method is used to play media. + */ + void playMedia(); + + /** + * Setup expectations: media test app is open and Minimize control bar present. + * + * This method is used to pause media. + */ + void pauseMedia(); + + /** + * Setup expectations: media test app is open and Minimize control bar present. + * + * This method is used to select next track. + */ + void clickNextTrack(); + + /** + * Setup expectations: media test app is open and Minimize control bar present. + * + * This method is used to select previous track. + */ + void clickPreviousTrack(); + + /** + * Setup expectations: media test app is open and Minimize control bar present. + * + * @return to get current playing track name from home screen. + */ + String getTrackName(); + + /** + * Setup expectations: media test app is open and Minimize control bar present. + * + * This method is used to maximize the play back screen. + */ + void maximizeNowPlaying(); +} diff --git a/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoMediaCenterNowPlayingHelper.java b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoMediaCenterNowPlayingHelper.java new file mode 100644 index 000000000..bbe0e2bc7 --- /dev/null +++ b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoMediaCenterNowPlayingHelper.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.platform.helpers; + +public interface IAutoMediaCenterNowPlayingHelper extends IAppHelper { + + /** + * Setup expectations: Now Playing is open. + * + * This method is used to play media. + */ + void playMedia(); + + /** + * Setup expectations: Now Playing is open. + * + * This method is used to pause media. + */ + void pauseMedia(); + + /** + * Setup expectations: Now Playing is open. + * + * This method is used to select next track. + */ + void clickNextTrack(); + + /** + * Setup expectations: Now Playing is open. + * + * This method is used to select previous track. + */ + void clickPreviousTrack(); + + /** + * Setup expectations: Now Playing is open. + * + * @return to get current playing track name from home screen. + */ + String getTrackName(); + + /** + * Setup expectations: Now Playing is open. + * + * This method is used to minimize now playing. + */ + void minimizeNowPlaying(); +}
\ No newline at end of file diff --git a/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoMediaHelper.java b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoMediaHelper.java index 035d68fad..ada56e72f 100644 --- a/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoMediaHelper.java +++ b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoMediaHelper.java @@ -93,4 +93,24 @@ public interface IAutoMediaHelper extends IAppHelper { * @return to get current playing track name. */ String getMediaTrackName(); + + /** + * Setup expectations: on home screen. + * + * @return to get current playing track name from home screen. + */ + String getMediaTrackNameFromHomeScreen(); + + /** + * Setup expectations: Media app is open. User navigates to sub-page of the Media Player + * + * <p>This method is to go back to the Media Player main page from any sub-page. + */ + void goBackToMediaHomePage(); + + /** + * This method is used to check if media is currently playing Returns true if media is playing + * else returns false + */ + boolean isPlaying(); } diff --git a/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoNotificationHelper.java b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoNotificationHelper.java index ac18ada0d..b5fd0f72d 100644 --- a/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoNotificationHelper.java +++ b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoNotificationHelper.java @@ -16,4 +16,36 @@ package android.platform.helpers; -public interface IAutoNotificationHelper extends INotificationHelper, Scrollable {} +public interface IAutoNotificationHelper extends INotificationHelper, Scrollable, IAppHelper { + /** + * Setup expectations: Notification app is open and scrolled to the bottom. + * + * <p>Tap clear all button if present. + */ + void tapClearAllBtn(); + + /** + * Setup expectations: A notification is received. + * + * <p>Check whether notification has been posted. + * + * @param title of the notification to be checked. + */ + boolean checkNotificationExists(String title); + + /** + * Setup expectations: A notification is received. + * + * <p>Swipe away a received notification. + * + * @param title of the notification to be swiped. + */ + void removeNotification(String title); + + /** + * Setup expectations: None. + * + * <p>Swipe down from status bar to open notifications. + */ + void openNotification(); +} diff --git a/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoNotificationMockingHelper.java b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoNotificationMockingHelper.java new file mode 100644 index 000000000..9903832ba --- /dev/null +++ b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/IAutoNotificationMockingHelper.java @@ -0,0 +1,3 @@ +package android.platform.helpers; + +public interface IAutoNotificationMockingHelper extends INotificationHelper, IAppHelper {} diff --git a/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/utility/Scrollable.java b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/utility/Scrollable.java index 7c8bd714b..c61dc2525 100644 --- a/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/utility/Scrollable.java +++ b/libraries/app-helpers/interfaces/auto/src/android/platform/helpers/utility/Scrollable.java @@ -27,6 +27,8 @@ import androidx.test.InstrumentationRegistry; * This interface is intended to be inherited by AppHelper classes to add scrolling functionlity. */ public interface Scrollable { + int DEFAULT_MARGIN = 5; + /** * Setup expectations: None * @@ -89,6 +91,81 @@ public interface Scrollable { } /** + * Setup expectations: None. + * + * <p>This method can be implemented optionally if customized margin is required. + * + * @return the gesture margin for scrolling. + */ + public default Margin getScrollableMargin() { + return new Margin(DEFAULT_MARGIN); + } + + /** + * Setup expectations: None. + * + * <p>This method can be implemented optionally if customized margin is required. It sets the + * gesture margin returned by <code>getScrollableMargin()</code>. + * + * @param margin Left, top, right and bottom margins will all be set this this value. + */ + public default void setScrollableMargin(int margin) { + throw new UnsupportedOperationException("setScrollableMargin method not implemeneted."); + } + + /** + * Setup expectations: None. + * + * <p>This method can be implemented optionally if customized margin is required. It sets the + * gesture margin returned by <code>getScrollableMargin()</code>. + * + * @param left The value to which to set the left margin for scrollling. + * @param top The value to which to set the top margin for scrollling. + * @param right The value to which to set the right margin for scrollling. + * @param bottom The value to which to set the bottom margin for scrollling. + */ + public default void setScrollableMargin(int left, int top, int right, int bottom) { + throw new UnsupportedOperationException("setScrollableMargin method not implemeneted."); + } + + public class Margin { + private int mLeft; + private int mTop; + private int mRight; + private int mBottom; + + public Margin(int margin) { + mLeft = margin; + mTop = margin; + mRight = margin; + mBottom = margin; + } + + public Margin(int left, int top, int right, int bottom) { + mLeft = left; + mTop = top; + mRight = right; + mBottom = bottom; + } + + public int getLeft() { + return mLeft; + } + + public int getTop() { + return mTop; + } + + public int getRight() { + return mRight; + } + + public int getBottom() { + return mBottom; + } + } + + /** * This is not part of the public interface. For internal use only. * * <p>Scroll in <code>direction</code> direction by <code>percent</code> percent of the whole @@ -105,6 +182,12 @@ public interface Scrollable { UiObject2 scrollable = device.findObject(By.scrollable(true)); if (scrollable != null) { + Margin margin = getScrollableMargin(); + scrollable.setGestureMargins( + margin.getLeft(), + margin.getTop(), + margin.getRight(), + margin.getBottom()); int scrollSpeed = calcScrollSpeed(scrollable, durationMs); scrollable.scroll(direction, percent / 100, scrollSpeed); } else { diff --git a/libraries/app-helpers/interfaces/handheld/src/android/platform/helpers/IPhotosHelper.java b/libraries/app-helpers/interfaces/handheld/src/android/platform/helpers/IPhotosHelper.java index 85b243d15..15684fdd4 100644 --- a/libraries/app-helpers/interfaces/handheld/src/android/platform/helpers/IPhotosHelper.java +++ b/libraries/app-helpers/interfaces/handheld/src/android/platform/helpers/IPhotosHelper.java @@ -112,22 +112,22 @@ public interface IPhotosHelper extends IAppHelper { public void openPicture(int index); /** - * Setup expectations: Photos is open and a picture album is open. + * Setup expectations: Photos is open and a picture is open. * - * This method will scroll the picture album in the specified direction. + * <p>This method will scroll to next or previous picture in the specified direction. * * @param direction The direction to scroll, must be LEFT or RIGHT. - * @return Returns whether album can be still scrolled in the given direction + * @return Returns whether picture can be still scrolled in the given direction */ - public boolean scrollAlbum(Direction direction); + public boolean scrollPicture(Direction direction); /** - * Setup expectations: Photos is open and a picture folder is open. + * Setup expectations: Photos is open and a page contains pictures or albums is open. * - * This method will scroll the Photos grid view in the specified direction. + * <p>This method will scroll the page in the specified direction. * * @param direction The direction of the scroll, must be UP or DOWN. * @return Returns whether the object can still scroll in the given direction */ - public boolean scrollGridView(Direction direction); + public boolean scrollPage(Direction direction); } diff --git a/libraries/collectors-helper/jank/src/com/android/helpers/SfStatsCollectionHelper.java b/libraries/collectors-helper/jank/src/com/android/helpers/SfStatsCollectionHelper.java new file mode 100644 index 000000000..b2c09480f --- /dev/null +++ b/libraries/collectors-helper/jank/src/com/android/helpers/SfStatsCollectionHelper.java @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.helpers; + +import static com.android.helpers.MetricUtility.constructKey; + +import android.support.test.uiautomator.UiDevice; +import android.util.Log; +import androidx.annotation.VisibleForTesting; +import androidx.test.InstrumentationRegistry; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +/** + * An {@link ICollectorHelper} for collecting SurfaceFlinger time stats. + * + * <p>This parses the output of {@code dumpsys SurfaceFlinger --timestats} and returns a collection + * of both global metrics and metrics tracked for each layer. + */ +public class SfStatsCollectionHelper implements ICollectorHelper<Double> { + + private static final String LOG_TAG = SfStatsCollectionHelper.class.getSimpleName(); + + private static final Pattern KEY_VALUE_PATTERN = + Pattern.compile("^(\\w+)\\s+=\\s+(\\d+\\.?\\d*|.*).*"); + private static final Pattern HISTOGRAM_PATTERN = + Pattern.compile("([^\\n]+)\\n((\\d+ms=\\d+\\s+)+)"); + + private static final String FRAME_DURATION_KEY = "frameDuration histogram is as below:"; + private static final String RENDER_ENGINE_KEY = "renderEngineTiming histogram is as below:"; + + @VisibleForTesting static final String SFSTATS_METRICS_PREFIX = "SFSTATS"; + + @VisibleForTesting static final String SFSTATS_COMMAND = "dumpsys SurfaceFlinger --timestats "; + + @VisibleForTesting + static final String SFSTATS_COMMAND_ENABLE_AND_CLEAR = SFSTATS_COMMAND + "-enable -clear"; + + @VisibleForTesting static final String SFSTATS_COMMAND_DUMP = SFSTATS_COMMAND + "-dump"; + + @VisibleForTesting + static final String SFSTATS_COMMAND_DISABLE_AND_CLEAR = SFSTATS_COMMAND + "-disable -clear"; + + private UiDevice mDevice; + + @Override + public boolean startCollecting() { + try { + getDevice().executeShellCommand(SFSTATS_COMMAND_ENABLE_AND_CLEAR); + } catch (Exception e) { + Log.e(LOG_TAG, "Encountered exception enabling dumpsys SurfaceFlinger.", e); + throw new RuntimeException(e); + } + return true; + } + + @Override + public Map<String, Double> getMetrics() { + Map<String, Double> results = new HashMap<>(); + String output; + try { + output = getDevice().executeShellCommand(SFSTATS_COMMAND_DUMP); + } catch (Exception e) { + Log.e(LOG_TAG, "Encountered exception calling dumpsys SurfaceFlinger.", e); + throw new RuntimeException(e); + } + String[] blocks = output.split("\n\n"); + + HashMap<String, String> globalPairs = getStatPairs(blocks[0]); + Map<String, Histogram> histogramPairs = getHistogramPairs(blocks[0]); + + for (String key : globalPairs.keySet()) { + String metricKey = constructKey(SFSTATS_METRICS_PREFIX, "GLOBAL", key.toUpperCase()); + results.put(metricKey, Double.valueOf(globalPairs.get(key))); + } + + if (histogramPairs.containsKey(FRAME_DURATION_KEY)) { + results.put( + constructKey(SFSTATS_METRICS_PREFIX, "GLOBAL", "FRAME_CPU_DURATION_AVG"), + histogramPairs.get(FRAME_DURATION_KEY).mean()); + } + + if (histogramPairs.containsKey(RENDER_ENGINE_KEY)) { + results.put( + constructKey(SFSTATS_METRICS_PREFIX, "GLOBAL", "RENDER_ENGINE_DURATION_AVG"), + histogramPairs.get(RENDER_ENGINE_KEY).mean()); + } + + for (int i = 1; i < blocks.length; i++) { + HashMap<String, String> layerPairs = getStatPairs(blocks[i]); + String layerName = layerPairs.get("layerName"); + String totalFrames = layerPairs.get("totalFrames"); + String droppedFrames = layerPairs.get("droppedFrames"); + String averageFPS = layerPairs.get("averageFPS"); + results.put( + constructKey(SFSTATS_METRICS_PREFIX, layerName, "TOTAL_FRAMES"), + Double.valueOf(totalFrames)); + results.put( + constructKey(SFSTATS_METRICS_PREFIX, layerName, "DROPPED_FRAMES"), + Double.valueOf(droppedFrames)); + results.put( + constructKey(SFSTATS_METRICS_PREFIX, layerName, "AVERAGE_FPS"), + Double.valueOf(averageFPS)); + } + + return results; + } + + @Override + public boolean stopCollecting() { + try { + getDevice().executeShellCommand(SFSTATS_COMMAND_DISABLE_AND_CLEAR); + } catch (Exception e) { + Log.e(LOG_TAG, "Encountered exception disabling dumpsys SurfaceFlinger.", e); + throw new RuntimeException(e); + } + return true; + } + + /** Returns the {@link UiDevice} under test. */ + @VisibleForTesting + protected UiDevice getDevice() { + if (mDevice == null) { + mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + } + return mDevice; + } + + /** + * Returns a map of key-value pairs for every line of timestats for each layer handled by + * SurfaceFlinger as well as some global SurfaceFlinger stats. An output line like {@code + * totalFrames = 42} would get parsed and be accessable as {@code pairs.get("totalFrames") => + * "42"} + */ + private HashMap<String, String> getStatPairs(String block) { + HashMap<String, String> pairs = new HashMap<>(); + String[] lines = block.split("\n"); + for (int j = 0; j < lines.length; j++) { + Matcher keyValueMatcher = KEY_VALUE_PATTERN.matcher(lines[j].trim()); + if (keyValueMatcher.matches()) { + pairs.put(keyValueMatcher.group(1), keyValueMatcher.group(2)); + } + } + return pairs; + } + + /** + * Returns a map of {@link Histogram} instances emitted by SurfaceFlinger stats. + * + * <p>Input must be of the format defined by the {@link HISTOGRAM_PATTERN} regex. Example input + * may include: + * + * <pre>{@code + * Sample key: + * 0ms=0 1ms=1 2ms=4 3ms=9 4ms=16 + * }</pre> + * + * <p>The corresponding output would include "Sample key:" as the key for a {@link Histogram} + * instance constructed from the string {@code 0ms=0 1ms=1 2ms=4 3ms=9 4ms=16}. + */ + private Map<String, Histogram> getHistogramPairs(String block) { + Map<String, Histogram> pairs = new HashMap<>(); + Matcher histogramMatcher = HISTOGRAM_PATTERN.matcher(block); + while (histogramMatcher.find()) { + String key = histogramMatcher.group(1); + String histogramString = histogramMatcher.group(2); + Histogram histogram = new Histogram(); + Stream.of(histogramString.split("\\s+")) + .forEach( + bucket -> + histogram.put( + Integer.valueOf( + bucket.substring(0, bucket.indexOf("ms"))), + Long.valueOf( + bucket.substring(bucket.indexOf("=") + 1)))); + pairs.put(key, histogram); + } + return pairs; + } + + /** Representation for a statistical histogram */ + private static final class Histogram { + private final Map<Integer, Long> internalMap; + + /** Constructs a histogram instance. */ + Histogram() { + internalMap = new HashMap<>(); + } + + /** + * Puts a (key, value) pair in the histogram. + * + * <p>The key would represent the particular bucket that the value is inserted into. + */ + Histogram put(Integer key, Long value) { + internalMap.put(key, value); + return this; + } + + /** + * Computes the mean of the histogram + * + * @return 0 if the histogram is empty, the true mean otherwise. + */ + double mean() { + long count = internalMap.values().stream().mapToLong(v -> v).sum(); + if (count <= 0) { + return 0.0; + } + long numerator = + internalMap + .entrySet() + .stream() + .mapToLong(entry -> entry.getKey() * entry.getValue()) + .sum(); + return (double) numerator / count; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Histogram)) { + return false; + } + + Histogram other = (Histogram) obj; + + return internalMap.equals(other.internalMap); + } + + @Override + public int hashCode() { + return internalMap.hashCode(); + } + } +} diff --git a/libraries/collectors-helper/jank/test/src/com/android/helpers/SfStatsCollectionHelperTest.java b/libraries/collectors-helper/jank/test/src/com/android/helpers/SfStatsCollectionHelperTest.java new file mode 100644 index 000000000..c986a6750 --- /dev/null +++ b/libraries/collectors-helper/jank/test/src/com/android/helpers/SfStatsCollectionHelperTest.java @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.helpers; + +import static com.android.helpers.MetricUtility.constructKey; +import static com.android.helpers.SfStatsCollectionHelper.SFSTATS_COMMAND_DISABLE_AND_CLEAR; +import static com.android.helpers.SfStatsCollectionHelper.SFSTATS_COMMAND_DUMP; +import static com.android.helpers.SfStatsCollectionHelper.SFSTATS_COMMAND_ENABLE_AND_CLEAR; +import static com.android.helpers.SfStatsCollectionHelper.SFSTATS_METRICS_PREFIX; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.when; + +import android.support.test.uiautomator.UiDevice; +import java.io.IOException; +import java.util.Map; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +public class SfStatsCollectionHelperTest { + private static final String SFSTATS_DUMP = + "SurfaceFlinger TimeStats:\n" + + "statsStart = 1566336030\n" + + "statsEnd = 1566338516\n" + + "totalFrames = 5791\n" + + "missedFrames = 82\n" + + "clientCompositionFrames = 0\n" + + "displayOnTime = 2485421 ms\n" + + "displayConfigStats is as below:\n" + + "90fps=3700ms 60fps=297492ms\n" + + "totalP2PTime = 2674034 ms\n" + + "frameDuration histogram is as below:\n" + + "0ms=0 1ms=0 2ms=0 3ms=0 4ms=0 5ms=50 6ms=50 7ms=0 8ms=0 9ms=0\n" + + "renderEngineTiming histogram is as below:\n" + + "0ms=0 1ms=0 2ms=0 3ms=0 4ms=0 5ms=0 6ms=50 7ms=50 8ms=0 9ms=0\n" + + "presentToPresent histogram is as below:\n" + + "0ms=0 1ms=0 2ms=0 3ms=0 4ms=0 5ms=0 6ms=0 7ms=0 8ms=5791 9ms=0\n" + + "\n" + + "layerName = com.breel.wallpapers19.DoodleWallpaperV1#0\n" + + "packageName = com.breel.wallpapers19.DoodleWallpaperV1\n" + + "totalFrames = 5448\n" + + "droppedFrames = 4\n" + + "averageFPS = 2.038\n" + + "present2present histogram is as below:\n" + + "0ms=0 1ms=0 2ms=0 3ms=0 4ms=0 5ms=0 6ms=0 7ms=0 8ms=5448 9ms=0\n" + + "latch2present histogram is as below:\n" + + "0ms=0 1ms=0 2ms=0 3ms=0 4ms=0 5ms=0 6ms=0 7ms=0 8ms=5448 9ms=0\n" + + "desired2present histogram is as below:\n" + + "0ms=0 1ms=0 2ms=0 3ms=0 4ms=0 5ms=0 6ms=0 7ms=0 8ms=5448 9ms=0\n" + + "acquire2present histogram is as below:\n" + + "0ms=0 1ms=0 2ms=0 3ms=0 4ms=0 5ms=0 6ms=0 7ms=0 8ms=5448 9ms=0\n" + + "post2present histogram is as below:\n" + + "0ms=0 1ms=0 2ms=0 3ms=0 4ms=0 5ms=0 6ms=0 7ms=0 8ms=5448 9ms=0\n" + + "post2acquire histogram is as below:\n" + + "0ms=0 1ms=0 2ms=0 3ms=0 4ms=0 5ms=0 6ms=0 7ms=0 8ms=5448 9ms=0\n" + + "\n" + + "layerName = com.google.android.nexuslauncher.NexusLauncherActivity#0\n" + + "packageName = com.google.android.nexuslauncher\n" + + "totalFrames = 264\n" + + "droppedFrames = 7\n" + + "averageFPS = 84.318\n" + + "present2present histogram is as below:\n" + + "0ms=0 1ms=0 2ms=0 3ms=0 4ms=0 5ms=0 6ms=0 7ms=0 8ms=264 9ms=0\n" + + "latch2present histogram is as below:\n" + + "0ms=0 1ms=0 2ms=0 3ms=0 4ms=0 5ms=0 6ms=0 7ms=0 8ms=264 9ms=0\n" + + "desired2present histogram is as below:\n" + + "0ms=0 1ms=0 2ms=0 3ms=0 4ms=0 5ms=0 6ms=0 7ms=0 8ms=264 9ms=0\n" + + "acquire2present histogram is as below:\n" + + "0ms=0 1ms=0 2ms=0 3ms=0 4ms=0 5ms=0 6ms=0 7ms=0 8ms=264 9ms=0\n" + + "post2present histogram is as below:\n" + + "0ms=0 1ms=0 2ms=0 3ms=0 4ms=0 5ms=0 6ms=0 7ms=0 8ms=264 9ms=0\n" + + "post2acquire histogram is as below:\n" + + "0ms=0 1ms=0 2ms=0 3ms=0 4ms=0 5ms=0 6ms=0 7ms=0 8ms=264 9ms=0\n" + + "\n" + + "layerName = SurfaceView - com.mxtech.videoplayer.ad/com.mxtech.videoplayer.ad.ActivityScreen#0\n" + + "packageName = \n" + + "totalFrames = 2352\n" + + "droppedFrames = 0\n" + + "averageFPS = 59.999\n" + + "present2present histogram is as below:\n" + + "0ms=0 1ms=0 2ms=0 3ms=0 4ms=0 5ms=0 6ms=0 7ms=0 8ms=2352 9ms=0\n" + + "latch2present histogram is as below:\n" + + "0ms=0 1ms=0 2ms=0 3ms=0 4ms=0 5ms=0 6ms=0 7ms=0 8ms=2352 9ms=0\n" + + "desired2present histogram is as below:\n" + + "0ms=0 1ms=0 2ms=0 3ms=0 4ms=0 5ms=0 6ms=0 7ms=0 8ms=2352 9ms=0\n" + + "acquire2present histogram is as below:\n" + + "0ms=0 1ms=0 2ms=0 3ms=0 4ms=0 5ms=0 6ms=0 7ms=0 8ms=2352 9ms=0\n" + + "post2present histogram is as below:\n" + + "0ms=0 1ms=0 2ms=0 3ms=0 4ms=0 5ms=0 6ms=0 7ms=0 8ms=2352 9ms=0\n" + + "post2acquire histogram is as below:\n" + + "0ms=0 1ms=0 2ms=0 3ms=0 4ms=0 5ms=0 6ms=0 7ms=0 8ms=2352 9ms=0"; + + private static final String LOG_TAG = SfStatsCollectionHelperTest.class.getSimpleName(); + + private @Mock UiDevice mUiDevice; + private SfStatsCollectionHelper mHelper; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mHelper = Mockito.spy(new SfStatsCollectionHelper()); + when(mHelper.getDevice()).thenReturn(mUiDevice); + } + + /** Test track a single, valid package. */ + @Test + public void testCollect_valuesMatch() throws Exception { + mockDumpCommand(); + mockEnableAndClearCommand(); + mockDisableAndClearCommand(); + mHelper.startCollecting(); + Map<String, Double> metrics = mHelper.getMetrics(); + assertThat( + metrics.get( + constructKey( + SFSTATS_METRICS_PREFIX, + "GLOBAL", + "statsStart".toUpperCase()))) + .isEqualTo(Double.valueOf(1566336030)); + assertThat( + metrics.get( + constructKey( + SFSTATS_METRICS_PREFIX, + "GLOBAL", + "statsEnd".toUpperCase()))) + .isEqualTo(Double.valueOf(1566338516)); + assertThat( + metrics.get( + constructKey( + SFSTATS_METRICS_PREFIX, + "GLOBAL", + "totalFrames".toUpperCase()))) + .isEqualTo(Double.valueOf(5791)); + assertThat( + metrics.get( + constructKey( + SFSTATS_METRICS_PREFIX, + "GLOBAL", + "missedFrames".toUpperCase()))) + .isEqualTo(Double.valueOf(82)); + assertThat( + metrics.get( + constructKey( + SFSTATS_METRICS_PREFIX, + "GLOBAL", + "clientCompositionFrames".toUpperCase()))) + .isEqualTo(Double.valueOf(0)); + assertThat( + metrics.get( + constructKey( + SFSTATS_METRICS_PREFIX, + "GLOBAL", + "displayOnTime".toUpperCase()))) + .isEqualTo(Double.valueOf(2485421)); + assertThat( + metrics.get( + constructKey( + SFSTATS_METRICS_PREFIX, + "GLOBAL", + "totalP2PTime".toUpperCase()))) + .isEqualTo(Double.valueOf(2674034)); + assertThat( + metrics.get( + constructKey( + SFSTATS_METRICS_PREFIX, + "GLOBAL", + "FRAME_CPU_DURATION_AVG"))) + .isEqualTo(Double.valueOf(5.5)); + assertThat( + metrics.get( + constructKey( + SFSTATS_METRICS_PREFIX, + "GLOBAL", + "RENDER_ENGINE_DURATION_AVG"))) + .isEqualTo(Double.valueOf(6.5)); + assertThat( + metrics.get( + constructKey( + SFSTATS_METRICS_PREFIX, + "com.breel.wallpapers19.DoodleWallpaperV1#0", + "TOTAL_FRAMES"))) + .isEqualTo(Double.valueOf(5448)); + assertThat( + metrics.get( + constructKey( + SFSTATS_METRICS_PREFIX, + "com.breel.wallpapers19.DoodleWallpaperV1#0", + "DROPPED_FRAMES"))) + .isEqualTo(Double.valueOf(4)); + assertThat( + metrics.get( + constructKey( + SFSTATS_METRICS_PREFIX, + "com.breel.wallpapers19.DoodleWallpaperV1#0", + "AVERAGE_FPS"))) + .isEqualTo(2.038); + assertThat( + metrics.get( + constructKey( + SFSTATS_METRICS_PREFIX, + "com.google.android.nexuslauncher.NexusLauncherActivity#0", + "TOTAL_FRAMES"))) + .isEqualTo(Double.valueOf(264)); + assertThat( + metrics.get( + constructKey( + SFSTATS_METRICS_PREFIX, + "com.google.android.nexuslauncher.NexusLauncherActivity#0", + "DROPPED_FRAMES"))) + .isEqualTo(Double.valueOf(7)); + assertThat( + metrics.get( + constructKey( + SFSTATS_METRICS_PREFIX, + "com.google.android.nexuslauncher.NexusLauncherActivity#0", + "AVERAGE_FPS"))) + .isEqualTo(84.318); + assertThat( + metrics.get( + constructKey( + SFSTATS_METRICS_PREFIX, + "SurfaceView - com.mxtech.videoplayer.ad/com.mxtech.videoplayer.ad.ActivityScreen#0", + "TOTAL_FRAMES"))) + .isEqualTo(Double.valueOf(2352)); + assertThat( + metrics.get( + constructKey( + SFSTATS_METRICS_PREFIX, + "SurfaceView - com.mxtech.videoplayer.ad/com.mxtech.videoplayer.ad.ActivityScreen#0", + "DROPPED_FRAMES"))) + .isEqualTo(Double.valueOf(0)); + assertThat( + metrics.get( + constructKey( + SFSTATS_METRICS_PREFIX, + "SurfaceView - com.mxtech.videoplayer.ad/com.mxtech.videoplayer.ad.ActivityScreen#0", + "AVERAGE_FPS"))) + .isEqualTo(59.999); + mHelper.stopCollecting(); + } + + private void mockEnableAndClearCommand() throws IOException { + when(mUiDevice.executeShellCommand(SFSTATS_COMMAND_ENABLE_AND_CLEAR)).thenReturn(""); + } + + private void mockDumpCommand() throws IOException { + when(mUiDevice.executeShellCommand(SFSTATS_COMMAND_DUMP)).thenReturn(SFSTATS_DUMP); + } + + private void mockDisableAndClearCommand() throws IOException { + when(mUiDevice.executeShellCommand(SFSTATS_COMMAND_DISABLE_AND_CLEAR)).thenReturn(""); + } +} diff --git a/libraries/collectors-helper/memory/src/com/android/helpers/FreeMemHelper.java b/libraries/collectors-helper/memory/src/com/android/helpers/FreeMemHelper.java index da0ff2c26..b2f1f8cb9 100644 --- a/libraries/collectors-helper/memory/src/com/android/helpers/FreeMemHelper.java +++ b/libraries/collectors-helper/memory/src/com/android/helpers/FreeMemHelper.java @@ -50,6 +50,7 @@ public class FreeMemHelper implements ICollectorHelper<Long> { private static final String PROC_MEMINFO = "cat /proc/meminfo"; private static final String LINE_SEPARATOR = "\\n"; private static final String MEM_AVAILABLE_PATTERN = "^MemAvailable.*"; + private static final String MEM_FREE_PATTERN = "^MemFree.*"; private static final Pattern CACHE_PROC_START_PATTERN = Pattern.compile(".*: Cached$"); private static final Pattern PID_PATTERN = Pattern.compile("^.*pid(?<processid> [0-9]*).*$"); private static final String DUMPSYS_PROCESS = "dumpsys meminfo %s"; @@ -57,6 +58,7 @@ public class FreeMemHelper implements ICollectorHelper<Long> { private static final String PROCESS_ID = "processid"; public static final String MEM_AVAILABLE_CACHE_PROC_DIRTY = "MemAvailable_CacheProcDirty_bytes"; public static final String PROC_MEMINFO_MEM_AVAILABLE= "proc_meminfo_memavailable_bytes"; + public static final String PROC_MEMINFO_MEM_FREE= "proc_meminfo_memfree_bytes"; public static final String DUMPSYS_CACHED_PROC_MEMORY= "dumpsys_cached_procs_memory_bytes"; private UiDevice mUiDevice; @@ -77,27 +79,35 @@ public class FreeMemHelper implements ICollectorHelper<Long> { String memInfo; try { memInfo = mUiDevice.executeShellCommand(PROC_MEMINFO); + Log.i(TAG, "cat proc/meminfo :" + memInfo); } catch (IOException ioe) { Log.e(TAG, "Failed to read " + PROC_MEMINFO + ".", ioe); return null; } Pattern memAvailablePattern = Pattern.compile(MEM_AVAILABLE_PATTERN, Pattern.MULTILINE); + Pattern memFreePattern = Pattern.compile(MEM_FREE_PATTERN, Pattern.MULTILINE); Matcher memAvailableMatcher = memAvailablePattern.matcher(memInfo); + Matcher memFreeMatcher = memFreePattern.matcher(memInfo); String[] memAvailable = null; - if (memAvailableMatcher.find()) { + String[] memFree = null; + if (memAvailableMatcher.find() && memFreeMatcher.find()) { memAvailable = memAvailableMatcher.group(0).split(SEPARATOR); + memFree = memFreeMatcher.group(0).split(SEPARATOR); } - if (memAvailable == null) { - Log.e(TAG, "MemAvailable is null."); + if (memAvailable == null || memFree == null) { + Log.e(TAG, "MemAvailable or MemFree is null."); return null; } Map<String, Long> results = new HashMap<>(); long memAvailableProc = Long.parseLong(memAvailable[1]); results.put(PROC_MEMINFO_MEM_AVAILABLE, (memAvailableProc * 1024)); + long memFreeProc = Long.parseLong(memFree[1]); + results.put(PROC_MEMINFO_MEM_FREE, (memFreeProc * 1024)); + long cacheProcDirty = memAvailableProc; byte[] dumpsysMemInfoBytes = MetricUtility.executeCommandBlocking(DUMPSYS_MEMIFNO, InstrumentationRegistry.getInstrumentation()); @@ -107,7 +117,7 @@ public class FreeMemHelper implements ICollectorHelper<Long> { for (String process : cachedProcList) { Log.i(TAG, "Cached Process" + process); Matcher match; - if (((match = matches(PID_PATTERN, process))) != null) { + if ((match = matches(PID_PATTERN, process)) != null) { String processId = match.group(PROCESS_ID); String processDumpSysMemInfo = String.format(DUMPSYS_PROCESS, processId); String processInfoStr; @@ -173,7 +183,7 @@ public class FreeMemHelper implements ICollectorHelper<Long> { Log.i(TAG, currLine); Matcher match; if (!isCacheProcSection - && ((match = matches(CACHE_PROC_START_PATTERN, currLine))) == null) { + && (match = matches(CACHE_PROC_START_PATTERN, currLine)) == null) { // Continue untill the start of cache proc section. continue; } else { diff --git a/libraries/collectors-helper/memory/src/com/android/helpers/RssSnapshotHelper.java b/libraries/collectors-helper/memory/src/com/android/helpers/RssSnapshotHelper.java new file mode 100644 index 000000000..fc947a75c --- /dev/null +++ b/libraries/collectors-helper/memory/src/com/android/helpers/RssSnapshotHelper.java @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.helpers; + +import static com.android.helpers.MetricUtility.constructKey; + +import android.support.test.uiautomator.UiDevice; +import android.util.Log; +import androidx.test.InstrumentationRegistry; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.InputMismatchException; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Scanner; +import java.util.Set; +import java.util.UUID; + +/** + * Helper to collect rss snapshot for a list of processes. + */ +public class RssSnapshotHelper implements ICollectorHelper<String> { + private static final String TAG = RssSnapshotHelper.class.getSimpleName(); + + private static final String DROP_CACHES_CMD = "echo %d > /proc/sys/vm/drop_caches"; + private static final String PIDOF_CMD = "pidof %s"; + public static final String ALL_PROCESSES_CMD = "ps -A"; + private static final String SHOWMAP_CMD = "showmap -v %d"; + + public static final String RSS_METRIC_PREFIX = "showmap_rss_bytes"; + public static final String OUTPUT_FILE_PATH_KEY = "showmap_output_file"; + public static final String RSS_PROCESS_COUNT = "rss_process_count"; + + private String[] mProcessNames = null; + private String mTestOutputDir = null; + private String mTestOutputFile = null; + + private int mDropCacheOption; + private boolean mCollectForAllProcesses = false; + private UiDevice mUiDevice; + + // Map to maintain per-process rss. + private Map<String, String> mRssMap = new HashMap<>(); + + public void setUp(String testOutputDir, String... processNames) { + mProcessNames = processNames; + mTestOutputDir = testOutputDir; + mDropCacheOption = 0; + mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + } + + @Override + public boolean startCollecting() { + if (mTestOutputDir == null) { + Log.e(TAG, String.format("Invalid test setup")); + return false; + } + + File directory = new File(mTestOutputDir); + String filePath = + String.format("%s/rss_snapshot%d.txt", mTestOutputDir, UUID.randomUUID().hashCode()); + File file = new File(filePath); + + // Make sure directory exists and file does not + if (directory.exists()) { + if (file.exists() && !file.delete()) { + Log.e(TAG, String.format("Failed to delete result output file %s", filePath)); + return false; + } + } else { + if (!directory.mkdirs()) { + Log.e(TAG, String.format("Failed to create result output directory %s", mTestOutputDir)); + return false; + } + } + + // Create an empty file to fail early in case there are no write permissions + try { + if (!file.createNewFile()) { + // This should not happen unless someone created the file right after we deleted it + Log.e(TAG, String.format("Race with another user of result output file %s", filePath)); + return false; + } + } catch (IOException e) { + Log.e(TAG, String.format("Failed to create result output file %s", filePath), e); + return false; + } + + mTestOutputFile = filePath; + return true; + } + + @Override + public Map<String, String> getMetrics() { + try { + // Drop cache if requested + if (mDropCacheOption > 0) { + dropCache(mDropCacheOption); + } + + if (mCollectForAllProcesses) { + Log.i(TAG, "Collecting RSS metrics for all processes."); + mProcessNames = getAllProcessNames(); + } else if (mProcessNames.length > 0) { + Log.i(TAG, "Collecting RSS only for given list of process"); + } else if (mProcessNames.length == 0) { + // No processes specified, just return empty map + return mRssMap; + } + + FileWriter writer = new FileWriter(new File(mTestOutputFile), true); + for (String processName : mProcessNames) { + List<Integer> pids = new ArrayList<>(); + + long totalrss = 0; + // Collect required data + try { + pids = getPids(processName); + + for (Integer pid: pids) { + String showmapOutput = execShowMap(processName, pid); + long rss = extractTotalRss(processName, showmapOutput); + // Track the total rss for the processes with the same process name. + totalrss += rss; + // Store showmap output into file. If there are more than one process + // with same name write the individual showmap associated with pid. + storeToFile(mTestOutputFile, processName, pid, showmapOutput, writer); + } + } catch (RuntimeException e) { + Log.e(TAG, e.getMessage(), e.getCause()); + // Skip this process and continue with the next one + continue; + } + + // Store metrics + mRssMap.put(constructKey(RSS_METRIC_PREFIX, processName), Long.toString(totalrss * 1024)); + // Store the unique process count. + mRssMap.put(RSS_PROCESS_COUNT, Integer.toString(mProcessNames.length)); + } + writer.close(); + mRssMap.put(OUTPUT_FILE_PATH_KEY, mTestOutputFile); + } catch (RuntimeException e) { + Log.e(TAG, e.getMessage(), e.getCause()); + } catch (IOException e) { + Log.e(TAG, String.format("Failed to write output file %s", mTestOutputFile), e); + } + + return mRssMap; + } + + @Override + public boolean stopCollecting() { + return true; + } + + /** + * Set drop cache option. + * + * @param dropCacheOption drop pagecache (1), slab (2) or all (3) cache + * @return true on success, false if input option is invalid + */ + public boolean setDropCacheOption(int dropCacheOption) { + // Valid values are 1..3 + if (dropCacheOption < 1 || dropCacheOption > 3) { + return false; + } + + mDropCacheOption = dropCacheOption; + return true; + } + + /** + * Drops kernel memory cache. + * + * @param cacheOption drop pagecache (1), slab (2) or all (3) caches + */ + private void dropCache(int cacheOption) throws RuntimeException { + try { + mUiDevice.executeShellCommand(String.format(DROP_CACHES_CMD, cacheOption)); + } catch (IOException e) { + throw new RuntimeException("Unable to drop caches", e); + } + } + + /** + * Get pid's of the process with {@code processName} name. + * + * @param processName name of the process to get pid + * @return pid's of the specified process + */ + private List<Integer> getPids(String processName) throws RuntimeException { + try { + String pidofOutput = mUiDevice.executeShellCommand(String.format(PIDOF_CMD, processName)); + + // Sample output for the process with more than 1 pid. + // Sample command : "pidof init" + // Sample output : 1 559 + String[] pids = pidofOutput.split("\\s+"); + List<Integer> pidList = new ArrayList<>(); + for (String pid: pids) { + pidList.add(Integer.parseInt(pid.trim())); + } + return pidList; + } catch (IOException e) { + throw new RuntimeException(String.format("Unable to get pid of %s ", processName), e); + } + } + + /** + * Executes showmap command for the process with {@code processName} name and {@code pid} pid. + * + * @param processName name of the process to run showmap for + * @param pid pid of the process to run showmap for + * @return the output of showmap command + */ + private String execShowMap(String processName, long pid) throws IOException { + try { + return mUiDevice.executeShellCommand(String.format(SHOWMAP_CMD, pid)); + } catch (IOException e) { + throw new RuntimeException( + String.format("Unable to execute showmap command for %s ", processName), e); + } + } + + /** + * Extract total RSS from showmap command output for the process with {@code processName} name. + * + * @param processName name of the process to extract RSS for + * @param showmapOutput showmap command output + * @return total RSS of the process + */ + private long extractTotalRss(String processName, String showmapOutput) throws RuntimeException { + try { + int pos = showmapOutput.lastIndexOf("----"); + Scanner sc = new Scanner(showmapOutput.substring(pos)); + sc.next(); + sc.nextLong(); + return sc.nextLong(); + } catch (IndexOutOfBoundsException | InputMismatchException e) { + throw new RuntimeException( + String.format("Unexpected showmap format for %s ", processName), e); + } + } + + /** + * Store test results for one process into file. + * + * @param fileName name of the file being written + * @param processName name of the process + * @param pid pid of the process + * @param showmapOutput showmap command output + * @param writer file writer to write the data + */ + private void storeToFile(String fileName, String processName, long pid, String showmapOutput, + FileWriter writer) throws RuntimeException { + try { + writer.write(String.format(">>> %s (%d) <<<\n", processName, pid)); + writer.write(showmapOutput); + writer.write('\n'); + } catch (IOException e) { + throw new RuntimeException(String.format("Unable to write file %s ", fileName), e); + } + } + + /** + * Enables RSS collection for all processes. + */ + public void setAllProcesses() { + mCollectForAllProcesses = true; + } + + /** + * Get all process names running in the system. + */ + private String[] getAllProcessNames() { + Set<String> allProcessNames = new LinkedHashSet<>(); + try { + String psOutput = mUiDevice.executeShellCommand(ALL_PROCESSES_CMD); + // Split the lines + String allProcesses[] = psOutput.split("\\n"); + for (String invidualProcessDetails : allProcesses) { + Log.i(TAG, String.format("Process detail: %s", invidualProcessDetails)); + // Sample process detail line + // system 603 1 41532 5396 SyS_epoll+ 0 S servicemanager + String processSplit[] = invidualProcessDetails.split("\\s+"); + // Parse process name + String processName = processSplit[processSplit.length - 1].trim(); + // Include the process name which are not enclosed in []. + if (!processName.startsWith("[") && !processName.endsWith("]")) { + // Skip the first (i.e header) line from "ps -A" output. + if (processName.equalsIgnoreCase("NAME")) { + continue; + } + Log.i(TAG, String.format("Including the process %s", processName)); + allProcessNames.add(processName); + } + } + } catch (IOException ioe) { + throw new RuntimeException( + String.format("Unable execute all processes command %s ", ALL_PROCESSES_CMD), + ioe); + } + return allProcessNames.toArray(new String[0]); + } +} diff --git a/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/FreeMemHelperTest.java b/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/FreeMemHelperTest.java index 283ab3620..9770908f1 100644 --- a/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/FreeMemHelperTest.java +++ b/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/FreeMemHelperTest.java @@ -54,6 +54,7 @@ public class FreeMemHelperTest { assertTrue(freeMemMetrics.containsKey(FreeMemHelper.DUMPSYS_CACHED_PROC_MEMORY)); assertTrue(freeMemMetrics.get(FreeMemHelper.MEM_AVAILABLE_CACHE_PROC_DIRTY) > 0); assertTrue(freeMemMetrics.get(FreeMemHelper.PROC_MEMINFO_MEM_AVAILABLE) > 0); + assertTrue(freeMemMetrics.get(FreeMemHelper.PROC_MEMINFO_MEM_FREE) > 0); assertTrue(freeMemMetrics.get(FreeMemHelper.DUMPSYS_CACHED_PROC_MEMORY) > 0); } } diff --git a/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/RssSnapshotHelperTest.java b/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/RssSnapshotHelperTest.java new file mode 100644 index 000000000..6a1e17922 --- /dev/null +++ b/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/RssSnapshotHelperTest.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.helpers.tests; + +import static com.android.helpers.MetricUtility.constructKey; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import androidx.test.runner.AndroidJUnit4; +import com.android.helpers.RssSnapshotHelper; +import java.util.Map; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Android Unit tests for {@link RssSnapshotHelper}. + * + * To run: + * atest CollectorsHelperTest:com.android.helpers.tests.RssSnapshotHelperTest + */ +@RunWith(AndroidJUnit4.class) +public class RssSnapshotHelperTest { + private static final String TAG = RssSnapshotHelperTest.class.getSimpleName(); + + // Valid output file + private static final String VALID_OUTPUT_DIR = "/sdcard/test_results"; + // Invalid output file (no permissions to write) + private static final String INVALID_OUTPUT_DIR = "/data/local/tmp"; + + // Lists of process names + private static final String[] EMPTY_PROCESS_LIST = {}; + private static final String[] ONE_PROCESS_LIST = {"com.android.systemui"}; + private static final String[] TWO_PROCESS_LIST = {"com.android.systemui", "system_server"}; + private static final String[] NO_PROCESS_LIST = {null}; + + private RssSnapshotHelper mRssSnapshotHelper; + + @Before + public void setUp() { + mRssSnapshotHelper = new RssSnapshotHelper(); + } + + /** + * Test start collecting returns false if the helper has not been properly set up. + */ + @Test + public void testSetUpNotCalled() { + assertFalse(mRssSnapshotHelper.startCollecting()); + } + + /** + * Test invalid options for drop cache flag. + */ + @Test + public void testInvalidDropCacheOptions() { + assertFalse(mRssSnapshotHelper.setDropCacheOption(-1)); + assertFalse(mRssSnapshotHelper.setDropCacheOption(0)); + assertFalse(mRssSnapshotHelper.setDropCacheOption(4)); + } + + /** + * Test invalid options for drop cache flag. + */ + @Test + public void testValidDropCacheOptions() { + assertTrue(mRssSnapshotHelper.setDropCacheOption(1)); + assertTrue(mRssSnapshotHelper.setDropCacheOption(2)); + assertTrue(mRssSnapshotHelper.setDropCacheOption(3)); + } + + /** + * Test no metrics are sampled if process name is empty. + */ + @Test + public void testEmptyProcessName() { + mRssSnapshotHelper.setUp(VALID_OUTPUT_DIR, EMPTY_PROCESS_LIST); + Map<String, String> metrics = mRssSnapshotHelper.getMetrics(); + assertTrue(metrics.isEmpty()); + } + + /** + * Test sampling on a valid and running process. + */ + @Test + public void testValidFile() { + mRssSnapshotHelper.setUp(VALID_OUTPUT_DIR, ONE_PROCESS_LIST); + assertTrue(mRssSnapshotHelper.startCollecting()); + } + + /** + * Test sampling on using an invalid output file. + */ + @Test + public void testInvalidFile() { + mRssSnapshotHelper.setUp(INVALID_OUTPUT_DIR, ONE_PROCESS_LIST); + assertFalse(mRssSnapshotHelper.startCollecting()); + } + + /** + * Test getting metrics from one process. + */ + @Test + public void testGetMetrics_OneProcess() { + testProcessList(ONE_PROCESS_LIST); + } + + /** + * Test getting metrics from multiple processes process. + */ + @Test + public void testGetMetrics_MultipleProcesses() { + testProcessList(TWO_PROCESS_LIST); + } + + /** + * Test all process flag return more than 2 processes metrics atleast. + */ + @Test + public void testGetMetrics_AllProcess() { + mRssSnapshotHelper.setUp(VALID_OUTPUT_DIR, NO_PROCESS_LIST); + mRssSnapshotHelper.setAllProcesses(); + assertTrue(mRssSnapshotHelper.startCollecting()); + Map<String, String> metrics = mRssSnapshotHelper.getMetrics(); + assertTrue(metrics.size() > 2); + assertTrue(metrics.containsKey(RssSnapshotHelper.OUTPUT_FILE_PATH_KEY)); + + } + + + private void testProcessList(String... processNames) { + mRssSnapshotHelper.setUp(VALID_OUTPUT_DIR, processNames); + assertTrue(mRssSnapshotHelper.startCollecting()); + Map<String, String> metrics = mRssSnapshotHelper.getMetrics(); + assertFalse(metrics.isEmpty()); + for (String processName : processNames) { + assertTrue( + metrics.containsKey(constructKey(RssSnapshotHelper.RSS_METRIC_PREFIX, processName))); + } + assertTrue(metrics.containsKey(RssSnapshotHelper.OUTPUT_FILE_PATH_KEY)); + } +} diff --git a/libraries/collectors-helper/perfetto/src/com/android/helpers/PerfettoHelper.java b/libraries/collectors-helper/perfetto/src/com/android/helpers/PerfettoHelper.java index c1d7aeef8..abf92e25c 100644 --- a/libraries/collectors-helper/perfetto/src/com/android/helpers/PerfettoHelper.java +++ b/libraries/collectors-helper/perfetto/src/com/android/helpers/PerfettoHelper.java @@ -40,6 +40,8 @@ public class PerfettoHelper { private static final String PERFETTO_START_CMD = "perfetto --background -c %s%s -o %s"; private static final String PERFETTO_TMP_OUTPUT_FILE = "/data/misc/perfetto-traces/trace_output.pb"; + // Additional arg to indicate that the perfetto config file is text format. + private static final String PERFETTO_TXT_PROTO_ARG = " --txt"; // Command to stop (i.e kill) the perfetto tracing. private static final String PERFETTO_STOP_CMD = "pkill -INT perfetto"; // Command to check the perfetto process id. @@ -62,9 +64,10 @@ public class PerfettoHelper { * /data/misc/perfetto-traces/ folder in the device. * * @param configFileName used for collecting the perfetto trace. + * @param isTextProtoConfig true if the config file is textproto format otherwise false. * @return true if trace collection started successfully otherwise return false. */ - public boolean startCollecting(String configFileName) { + public boolean startCollecting(String configFileName, boolean isTextProtoConfig) { mUIDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); if (configFileName == null || configFileName.isEmpty()) { Log.e(LOG_TAG, "Perfetto config file name is null or empty."); @@ -79,15 +82,22 @@ public class PerfettoHelper { return false; } } + // Remove already existing temporary output trace file if any. String output = mUIDevice.executeShellCommand(String.format(REMOVE_CMD, PERFETTO_TMP_OUTPUT_FILE)); Log.i(LOG_TAG, String.format("Perfetto output file cleanup - %s", output)); + String perfettoCmd = String.format(PERFETTO_START_CMD, + PERFETTO_ROOT_DIR, configFileName, PERFETTO_TMP_OUTPUT_FILE); + + if(isTextProtoConfig) { + perfettoCmd = perfettoCmd + PERFETTO_TXT_PROTO_ARG; + } + // Start perfetto tracing. Log.i(LOG_TAG, "Starting perfetto tracing."); - String startOutput = mUIDevice.executeShellCommand(String.format(PERFETTO_START_CMD, - PERFETTO_ROOT_DIR, configFileName, PERFETTO_TMP_OUTPUT_FILE)); + String startOutput = mUIDevice.executeShellCommand(perfettoCmd); Log.i(LOG_TAG, String.format("Perfetto start command output - %s", startOutput)); // TODO : Once the output status is available use that for additional validation. if (!isPerfettoRunning()) { diff --git a/libraries/collectors-helper/perfetto/test/src/com/android/helpers/tests/PerfettoHelperTest.java b/libraries/collectors-helper/perfetto/test/src/com/android/helpers/tests/PerfettoHelperTest.java index 66b4f3bfe..a81499d01 100644 --- a/libraries/collectors-helper/perfetto/test/src/com/android/helpers/tests/PerfettoHelperTest.java +++ b/libraries/collectors-helper/perfetto/test/src/com/android/helpers/tests/PerfettoHelperTest.java @@ -37,6 +37,7 @@ import static org.junit.Assert.assertTrue; * * To run: * Have a valid perfetto config under /data/misc/perfetto-traces/valid_config.pb + * Have a valid text perfetto config under /data/misc/perfetto-traces/valid_text_config.textproto * TODO: b/119020380 to keep track of automating the above step. * atest CollectorsHelperTest:com.android.helpers.tests.PerfettoHelperTest */ @@ -64,7 +65,7 @@ public class PerfettoHelperTest { */ @Test public void testNullConfigName() throws Exception { - assertFalse(perfettoHelper.startCollecting(null)); + assertFalse(perfettoHelper.startCollecting(null, false)); } /** @@ -72,7 +73,7 @@ public class PerfettoHelperTest { */ @Test public void testEmptyConfigName() throws Exception { - assertFalse(perfettoHelper.startCollecting("")); + assertFalse(perfettoHelper.startCollecting("", false)); } /** @@ -80,7 +81,7 @@ public class PerfettoHelperTest { */ @Test public void testNoConfigFile() throws Exception { - assertFalse(perfettoHelper.startCollecting("no_config.pb")); + assertFalse(perfettoHelper.startCollecting("no_config.pb", false)); } /** @@ -88,7 +89,7 @@ public class PerfettoHelperTest { */ @Test public void testPerfettoStartSuccess() throws Exception { - assertTrue(perfettoHelper.startCollecting("valid_config.pb")); + assertTrue(perfettoHelper.startCollecting("valid_config.pb", false)); } /** @@ -96,7 +97,7 @@ public class PerfettoHelperTest { */ @Test public void testPerfettoValidOutputPath() throws Exception { - assertTrue(perfettoHelper.startCollecting("valid_config.pb")); + assertTrue(perfettoHelper.startCollecting("valid_config.pb", false)); assertTrue(perfettoHelper.stopCollecting(1000, "data/local/tmp/out.pb")); } @@ -105,7 +106,7 @@ public class PerfettoHelperTest { */ @Test public void testPerfettoInvalidOutputPath() throws Exception { - assertTrue(perfettoHelper.startCollecting("valid_config.pb")); + assertTrue(perfettoHelper.startCollecting("valid_config.pb", false)); // Don't have permission to create new folder under /data assertFalse(perfettoHelper.stopCollecting(1000, "/data/dummy/xyz/out.pb")); } @@ -116,7 +117,7 @@ public class PerfettoHelperTest { */ @Test public void testPerfettoSuccess() throws Exception { - assertTrue(perfettoHelper.startCollecting("valid_config.pb")); + assertTrue(perfettoHelper.startCollecting("valid_config.pb", false)); assertTrue(perfettoHelper.stopCollecting(1000, "/data/local/tmp/out.pb")); UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); String[] fileStats = uiDevice.executeShellCommand(String.format( @@ -125,4 +126,18 @@ public class PerfettoHelperTest { assertTrue(fileSize > 0); } + /** + * Test perfetto collection returns true and output file size greater than zero + * if the valid perfetto config file used. + */ + @Test + public void testTextProtoConfigSuccess() throws Exception { + assertTrue(perfettoHelper.startCollecting("valid_text_config.textproto", true)); + assertTrue(perfettoHelper.stopCollecting(1000, "/data/local/tmp/out.pb")); + UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + String[] fileStats = uiDevice.executeShellCommand(String.format( + FILE_SIZE_IN_BYTES, "/data/local/tmp/out.pb")).split(" "); + int fileSize = Integer.parseInt(fileStats[0].trim()); + assertTrue(fileSize > 0); + } } diff --git a/libraries/device-collectors/src/main/java/android/device/collectors/PerfettoListener.java b/libraries/device-collectors/src/main/java/android/device/collectors/PerfettoListener.java index 8c0891e5f..9ba0e8b9d 100644 --- a/libraries/device-collectors/src/main/java/android/device/collectors/PerfettoListener.java +++ b/libraries/device-collectors/src/main/java/android/device/collectors/PerfettoListener.java @@ -46,6 +46,8 @@ public class PerfettoListener extends BaseMetricListener { private static final String DEFAULT_WAIT_TIME_MSECS = "3000"; // Default output folder to store the perfetto output traces. private static final String DEFAULT_OUTPUT_ROOT = "/sdcard/test_results"; + // Argument to indicate the perfetto config file is text proto file. + public static final String PERFETTO_CONFIG_TEXT_PROTO = "perfetto_config_text_proto"; // Argument to get custom config file name for collecting the trace. private static final String PERFETTO_CONFIG_FILE_ARG = "perfetto_config_file"; // Argument to get custom time in millisecs to wait before dumping the trace. @@ -75,6 +77,7 @@ public class PerfettoListener extends BaseMetricListener { // Store the method name and invocation count to create unique file name for each trace. private Map<String, Integer> mTestIdInvocationCount = new HashMap<>(); private boolean mPerfettoStartSuccess = false; + private boolean mIsConfigTextProto = false; private boolean mIsCollectPerRun; private PerfettoHelper mPerfettoHelper = new PerfettoHelper(); @@ -123,10 +126,14 @@ public class PerfettoListener extends BaseMetricListener { // Whether to collect the for the entire test run or per test. mIsCollectPerRun = Boolean.parseBoolean(args.getString(COLLECT_PER_RUN)); + // Whether the config is text proto or not. By default set to false. + mIsConfigTextProto = Boolean.parseBoolean(args.getString(PERFETTO_CONFIG_TEXT_PROTO)); + // Perfetto config file has to be under /data/misc/perfetto-traces/ // defaulted to trace_config.pb is perfetto_config_file is not passed. mConfigFileName = args.getString(PERFETTO_CONFIG_FILE_ARG, DEFAULT_CONFIG_FILE); + // Whether to hold wakelocks on all Prefetto tracing functions. You may want to enable // this if your device is not USB connected. This option prevents the device from // going into suspend mode while this listener is running intensive tasks. @@ -288,16 +295,22 @@ public class PerfettoListener extends BaseMetricListener { @VisibleForTesting public void acquireWakelock(WakeLock wakelock) { if (wakelock != null) { + Log.d(getTag(), "wakelock.isHeld: " + wakelock.isHeld()); Log.d(getTag(), "acquiring wakelock."); wakelock.acquire(); + Log.d(getTag(), "wakelock acquired."); + Log.d(getTag(), "wakelock.isHeld: " + wakelock.isHeld()); } } @VisibleForTesting public void releaseWakelock(WakeLock wakelock) { if (wakelock != null) { + Log.d(getTag(), "wakelock.isHeld: " + wakelock.isHeld()); Log.d(getTag(), "releasing wakelock."); wakelock.release(); + Log.d(getTag(), "wakelock released."); + Log.d(getTag(), "wakelock.isHeld: " + wakelock.isHeld()); } } @@ -313,7 +326,8 @@ public class PerfettoListener extends BaseMetricListener { * Start perfetto tracing using the given config file. */ private void startPerfettoTracing() { - mPerfettoStartSuccess = mPerfettoHelper.startCollecting(mConfigFileName); + mPerfettoStartSuccess = mPerfettoHelper.startCollecting(mConfigFileName, + mIsConfigTextProto); if (!mPerfettoStartSuccess) { Log.e(getTag(), "Perfetto did not start successfully."); } diff --git a/libraries/device-collectors/src/main/java/android/device/collectors/RssSnapshotListener.java b/libraries/device-collectors/src/main/java/android/device/collectors/RssSnapshotListener.java new file mode 100644 index 000000000..fc82512c9 --- /dev/null +++ b/libraries/device-collectors/src/main/java/android/device/collectors/RssSnapshotListener.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.device.collectors; + +import android.device.collectors.annotations.OptionClass; +import android.os.Bundle; +import android.util.Log; +import androidx.annotation.VisibleForTesting; +import com.android.helpers.RssSnapshotHelper; +import java.util.HashMap; +import java.util.Map; + +/** + * A {@link RssSnapshotListener} that takes a snapshot of Rss sizes for the list of + * specified processes. + * + * Options: + * -e process-names [processNames] : a comma-separated list of processes + * -e drop-cache [pagecache | slab | all] : drop cache flag + * -e test-output-dir [path] : path to the output directory + */ +@OptionClass(alias = "rsssnapshot-collector") +public class RssSnapshotListener extends BaseCollectionListener<String> { + private static final String TAG = RssSnapshotListener.class.getSimpleName(); + private static final String DEFAULT_OUTPUT_DIR = "/sdcard/test_results"; + + @VisibleForTesting static final String PROCESS_SEPARATOR = ","; + @VisibleForTesting static final String PROCESS_NAMES_KEY = "process-names"; + @VisibleForTesting static final String DROP_CACHE_KEY = "drop-cache"; + @VisibleForTesting static final String OUTPUT_DIR_KEY = "test-output-dir"; + + private RssSnapshotHelper mRssSnapshotHelper = new RssSnapshotHelper(); + private final Map<String, Integer> dropCacheValues = new HashMap<String, Integer>() { + { + put("pagecache", 1); + put("slab", 2); + put("all", 3); + } + }; + + public RssSnapshotListener() { + createHelperInstance(mRssSnapshotHelper); + } + + /** + * Constructor to simulate receiving the instrumentation arguments. Should not be used except + * for testing. + */ + @VisibleForTesting + public RssSnapshotListener(Bundle args, RssSnapshotHelper helper) { + super(args, helper); + mRssSnapshotHelper = helper; + createHelperInstance(mRssSnapshotHelper); + } + + /** + * Adds the options for rss snapshot collector. + */ + @Override + public void setupAdditionalArgs() { + Bundle args = getArgsBundle(); + String testOutputDir = args.getString(OUTPUT_DIR_KEY, DEFAULT_OUTPUT_DIR); + // Collect for all processes if process list is empty or null. + String procsString = args.getString(PROCESS_NAMES_KEY); + + String[] procs = null; + if (procsString == null || procsString.isEmpty()) { + mRssSnapshotHelper.setAllProcesses(); + } else { + procs = procsString.split(PROCESS_SEPARATOR); + } + + mRssSnapshotHelper.setUp(testOutputDir, procs); + + String dropCacheValue = args.getString(DROP_CACHE_KEY); + if (dropCacheValue != null) { + if (dropCacheValues.containsKey(dropCacheValue)) { + mRssSnapshotHelper.setDropCacheOption(dropCacheValues.get(dropCacheValue)); + } else { + Log.e(TAG, "Value for \"" + DROP_CACHE_KEY + "\" parameter is invalid"); + return; + } + } + } +} diff --git a/libraries/device-collectors/src/main/java/android/device/collectors/SfStatsListener.java b/libraries/device-collectors/src/main/java/android/device/collectors/SfStatsListener.java new file mode 100644 index 000000000..fc03f0b38 --- /dev/null +++ b/libraries/device-collectors/src/main/java/android/device/collectors/SfStatsListener.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.device.collectors; + +import android.device.collectors.annotations.OptionClass; +import android.os.Bundle; +import androidx.annotation.VisibleForTesting; + +import com.android.helpers.SfStatsCollectionHelper; + +@OptionClass(alias = "sfstats-listener") +public class SfStatsListener extends BaseCollectionListener<Double> { + private static final String LOG_TAG = SfStatsListener.class.getSimpleName(); + + public SfStatsListener() { + createHelperInstance(new SfStatsCollectionHelper()); + } + + @VisibleForTesting + public SfStatsListener(Bundle args, SfStatsCollectionHelper helper) { + super(args, helper); + } +} diff --git a/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/full-battery-capacity/README.md b/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/full-battery-capacity/README.md new file mode 100644 index 000000000..e780cfd81 --- /dev/null +++ b/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/full-battery-capacity/README.md @@ -0,0 +1,3 @@ +# Full Battery Capacity Configs + +These statsd configs collects FullBatteryCapacity metrics. diff --git a/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/full-battery-capacity/full-battery-capacity-run-level.pb b/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/full-battery-capacity/full-battery-capacity-run-level.pb Binary files differnew file mode 100644 index 000000000..16a19a37f --- /dev/null +++ b/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/full-battery-capacity/full-battery-capacity-run-level.pb diff --git a/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/full-battery-capacity/full-battery-capacity-test-level.pb b/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/full-battery-capacity/full-battery-capacity-test-level.pb Binary files differnew file mode 100644 index 000000000..4018b1851 --- /dev/null +++ b/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/full-battery-capacity/full-battery-capacity-test-level.pb diff --git a/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/greenday-power-smaller/README.md b/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/greenday-power-smaller/README.md new file mode 100644 index 000000000..c496110ae --- /dev/null +++ b/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/greenday-power-smaller/README.md @@ -0,0 +1,3 @@ +# Greenday Power Configs for Run Level and Test Level Metrics + +Configs for a smaller set of power metrics : CPUTimePerFreq, MobileBytesTransfer, RemainingBatteryCapacity and WifiBytesTransfer.
\ No newline at end of file diff --git a/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/greenday-power-smaller/greenday-power-smaller-run-level.pb b/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/greenday-power-smaller/greenday-power-smaller-run-level.pb Binary files differnew file mode 100644 index 000000000..dd2698b46 --- /dev/null +++ b/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/greenday-power-smaller/greenday-power-smaller-run-level.pb diff --git a/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/greenday-power-smaller/greenday-power-smaller-test-level.pb b/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/greenday-power-smaller/greenday-power-smaller-test-level.pb Binary files differnew file mode 100644 index 000000000..43dc7c6d6 --- /dev/null +++ b/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/greenday-power-smaller/greenday-power-smaller-test-level.pb diff --git a/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/on-device-power-measurement/README.md b/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/on-device-power-measurement/README.md new file mode 100644 index 000000000..96dc0d661 --- /dev/null +++ b/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/on-device-power-measurement/README.md @@ -0,0 +1,4 @@ +# On Device Power Measurement Configs + +These configs are used to pull the on-device power measurement before and after a test and test run as defined in the +OnDevicePowerMeasurement atom. diff --git a/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/on-device-power-measurement/on-device-power-measurement-run-level.pb b/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/on-device-power-measurement/on-device-power-measurement-run-level.pb new file mode 100644 index 000000000..803e7bbbb --- /dev/null +++ b/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/on-device-power-measurement/on-device-power-measurement-run-level.pb @@ -0,0 +1,3 @@ +ò‘ÊÏÉŸí”ß*,±‚䃆܈š‹ûâǶÁóÑžë0èHXÐ`€Óž¶¤¢Ÿ„2:ûâǶÁóÑžë¶N:ꃂ÷œÇ”«/((:‚›Úï§âÛ6/((:#€Óž¶¤¢Ÿ„2‚›Úï§âÛ6ꃂ÷œÇ”«bAID_GRAPHICSb
AID_INCIDENTDb +AID_STATSDb AID_RADIOb +AID_SYSTEMbAID_ROOTb
AID_BLUETOOTHÀ>
\ No newline at end of file diff --git a/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/on-device-power-measurement/on-device-power-measurement-test-level.pb b/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/on-device-power-measurement/on-device-power-measurement-test-level.pb new file mode 100644 index 000000000..8387eee16 --- /dev/null +++ b/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/on-device-power-measurement/on-device-power-measurement-test-level.pb @@ -0,0 +1,3 @@ +µÔ¸îÂãø¿1*+Ÿ‹Ôÿÿùï[ûâǶÁóÑžë0èHXÐ`¾Ò°Þö¤¨ºi:ûâǶÁóÑžë¶N:§óæëξ£²³/((:ŸÓœÎ‚°¨µ„/((:$¾Ò°Þö¤¨ºiŸÓœÎ‚°¨µ„§óæëξ£²³bAID_GRAPHICSb
AID_INCIDENTDb +AID_STATSDb AID_RADIOb +AID_SYSTEMbAID_ROOTb
AID_BLUETOOTHÀ>
\ No newline at end of file diff --git a/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/remaining-battery-capacity/README.md b/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/remaining-battery-capacity/README.md index 558107aae..e3f313952 100644 --- a/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/remaining-battery-capacity/README.md +++ b/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/remaining-battery-capacity/README.md @@ -1,4 +1,4 @@ # Remaining Battery Capacity Configs These configs are used to collect the remaining battery capacity on the device as defined in the -RemainingBatteryCapacity (Colomb counter) atom. +RemainingBatteryCapacity (Colomb counter) atom. Also includes configs with uid to package name mapping diff --git a/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/remaining-battery-capacity/remaining-battery-capacity-with-uid-run-level.pb b/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/remaining-battery-capacity/remaining-battery-capacity-with-uid-run-level.pb Binary files differnew file mode 100644 index 000000000..f4a1d0a55 --- /dev/null +++ b/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/remaining-battery-capacity/remaining-battery-capacity-with-uid-run-level.pb diff --git a/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/remaining-battery-capacity/remaining-battery-capacity-with-uid-test-level.pb b/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/remaining-battery-capacity/remaining-battery-capacity-with-uid-test-level.pb Binary files differnew file mode 100644 index 000000000..4f534e1e8 --- /dev/null +++ b/libraries/device-collectors/src/main/platform-collectors/res/statsd-configs/remaining-battery-capacity/remaining-battery-capacity-with-uid-test-level.pb diff --git a/libraries/device-collectors/src/test/java/android/device/collectors/PerfettoListenerTest.java b/libraries/device-collectors/src/test/java/android/device/collectors/PerfettoListenerTest.java index c51ded8cf..20ae4d63c 100644 --- a/libraries/device-collectors/src/test/java/android/device/collectors/PerfettoListenerTest.java +++ b/libraries/device-collectors/src/test/java/android/device/collectors/PerfettoListenerTest.java @@ -18,6 +18,7 @@ package android.device.collectors; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doAnswer; @@ -113,14 +114,14 @@ public class PerfettoListenerTest { public void testPerfettoPerTestSuccessFlow() throws Exception { Bundle b = new Bundle(); mListener = initListener(b); - doReturn(true).when(mPerfettoHelper).startCollecting(anyString()); + doReturn(true).when(mPerfettoHelper).startCollecting(anyString(), anyBoolean()); doReturn(true).when(mPerfettoHelper).stopCollecting(anyLong(), anyString()); // Test run start behavior mListener.testRunStarted(mRunDesc); // Test test start behavior mListener.testStarted(mTest1Desc); - verify(mPerfettoHelper, times(1)).startCollecting(anyString()); + verify(mPerfettoHelper, times(1)).startCollecting(anyString(), anyBoolean()); mListener.onTestEnd(mDataRecord, mTest1Desc); verify(mPerfettoHelper, times(1)).stopCollecting(anyLong(), anyString()); @@ -135,14 +136,14 @@ public class PerfettoListenerTest { Bundle b = new Bundle(); b.putString(PerfettoListener.COLLECT_PER_RUN, "true"); mListener = initListener(b); - doReturn(true).when(mPerfettoHelper).startCollecting(anyString()); + doReturn(true).when(mPerfettoHelper).startCollecting(anyString(), anyBoolean()); doReturn(true).when(mPerfettoHelper).stopCollecting(anyLong(), anyString()); // Test run start behavior mListener.onTestRunStart(mListener.createDataRecord(), FAKE_DESCRIPTION); - verify(mPerfettoHelper, times(1)).startCollecting(anyString()); + verify(mPerfettoHelper, times(1)).startCollecting(anyString(), anyBoolean()); mListener.testStarted(mTest1Desc); - verify(mPerfettoHelper, times(1)).startCollecting(anyString()); + verify(mPerfettoHelper, times(1)).startCollecting(anyString(), anyBoolean()); mListener.onTestEnd(mDataRecord, mTest1Desc); verify(mPerfettoHelper, times(0)).stopCollecting(anyLong(), anyString()); mListener.onTestRunEnd(mListener.createDataRecord(), new Result()); @@ -185,7 +186,7 @@ public class PerfettoListenerTest { b.putString(PerfettoListener.COLLECT_PER_RUN, "true"); mListener = initListener(b); - doReturn(true).when(mPerfettoHelper).startCollecting(anyString()); + doReturn(true).when(mPerfettoHelper).startCollecting(anyString(), anyBoolean()); doReturn(true).when(mPerfettoHelper).stopCollecting(anyLong(), anyString()); // Test run start behavior @@ -204,7 +205,7 @@ public class PerfettoListenerTest { Bundle b = new Bundle(); mListener = initListener(b); - doReturn(true).when(mPerfettoHelper).startCollecting(anyString()); + doReturn(true).when(mPerfettoHelper).startCollecting(anyString(), anyBoolean()); doReturn(true).when(mPerfettoHelper).stopCollecting(anyLong(), anyString()); // Test run start behavior @@ -225,7 +226,7 @@ public class PerfettoListenerTest { b.putString(PerfettoListener.COLLECT_PER_RUN, "true"); mListener = initListener(b); - doReturn(true).when(mPerfettoHelper).startCollecting(anyString()); + doReturn(true).when(mPerfettoHelper).startCollecting(anyString(), anyBoolean()); doReturn(true).when(mPerfettoHelper).stopCollecting(anyLong(), anyString()); // Test run start behavior @@ -243,7 +244,7 @@ public class PerfettoListenerTest { b.putString(PerfettoListener.HOLD_WAKELOCK_WHILE_COLLECTING, "true"); mListener = initListener(b); - doReturn(true).when(mPerfettoHelper).startCollecting(anyString()); + doReturn(true).when(mPerfettoHelper).startCollecting(anyString(), anyBoolean()); doReturn(true).when(mPerfettoHelper).stopCollecting(anyLong(), anyString()); // Test run start behavior @@ -264,7 +265,7 @@ public class PerfettoListenerTest { b.putString(PerfettoListener.HOLD_WAKELOCK_WHILE_COLLECTING, "true"); mListener = initListener(b); - doReturn(true).when(mPerfettoHelper).startCollecting(anyString()); + doReturn(true).when(mPerfettoHelper).startCollecting(anyString(), anyBoolean()); doReturn(true).when(mPerfettoHelper).stopCollecting(anyLong(), anyString()); // Test run start behavior @@ -290,7 +291,7 @@ public class PerfettoListenerTest { b.putString(PerfettoListener.COLLECT_PER_RUN, "true"); mListener = initListener(b); - doReturn(true).when(mPerfettoHelper).startCollecting(anyString()); + doReturn(true).when(mPerfettoHelper).startCollecting(anyString(), anyBoolean()); doReturn(true).when(mPerfettoHelper).stopCollecting(anyLong(), anyString()); // Test run start behavior @@ -315,11 +316,11 @@ public class PerfettoListenerTest { Bundle b = new Bundle(); b.putString(PerfettoListener.COLLECT_PER_RUN, "true"); mListener = initListener(b); - doReturn(false).when(mPerfettoHelper).startCollecting(anyString()); + doReturn(false).when(mPerfettoHelper).startCollecting(anyString(), anyBoolean()); // Test run start behavior mListener.onTestRunStart(mListener.createDataRecord(), FAKE_DESCRIPTION); - verify(mPerfettoHelper, times(1)).startCollecting(anyString()); + verify(mPerfettoHelper, times(1)).startCollecting(anyString(), anyBoolean()); mListener.onTestRunEnd(mListener.createDataRecord(), new Result()); verify(mPerfettoHelper, times(0)).stopCollecting(anyLong(), anyString()); } @@ -331,14 +332,14 @@ public class PerfettoListenerTest { public void testPerfettoStartFailureFlow() throws Exception { Bundle b = new Bundle(); mListener = initListener(b); - doReturn(false).when(mPerfettoHelper).startCollecting(anyString()); + doReturn(false).when(mPerfettoHelper).startCollecting(anyString(), anyBoolean()); // Test run start behavior mListener.testRunStarted(mRunDesc); // Test test start behavior mListener.testStarted(mTest1Desc); - verify(mPerfettoHelper, times(1)).startCollecting(anyString()); + verify(mPerfettoHelper, times(1)).startCollecting(anyString(), anyBoolean()); mListener.onTestEnd(mDataRecord, mTest1Desc); verify(mPerfettoHelper, times(0)).stopCollecting(anyLong(), anyString()); } @@ -351,7 +352,7 @@ public class PerfettoListenerTest { public void testPerfettoInvocationCount() throws Exception { Bundle b = new Bundle(); mListener = initListener(b); - doReturn(true).when(mPerfettoHelper).startCollecting(anyString()); + doReturn(true).when(mPerfettoHelper).startCollecting(anyString(), anyBoolean()); doReturn(true).when(mPerfettoHelper).stopCollecting(anyLong(), anyString()); // Test run start behavior @@ -359,19 +360,19 @@ public class PerfettoListenerTest { // Test1 invocation 1 start behavior mListener.testStarted(mTest1Desc); - verify(mPerfettoHelper, times(1)).startCollecting(anyString()); + verify(mPerfettoHelper, times(1)).startCollecting(anyString(), anyBoolean()); mListener.onTestEnd(mDataRecord, mTest1Desc); verify(mPerfettoHelper, times(1)).stopCollecting(anyLong(), anyString()); // Test1 invocation 2 start behaviour mListener.testStarted(mTest1Desc); - verify(mPerfettoHelper, times(2)).startCollecting(anyString()); + verify(mPerfettoHelper, times(2)).startCollecting(anyString(), anyBoolean()); mListener.onTestEnd(mDataRecord, mTest1Desc); verify(mPerfettoHelper, times(2)).stopCollecting(anyLong(), anyString()); // Test2 invocation 1 start behaviour mListener.testStarted(mTest2Desc); - verify(mPerfettoHelper, times(3)).startCollecting(anyString()); + verify(mPerfettoHelper, times(3)).startCollecting(anyString(), anyBoolean()); mDataRecord = mListener.createDataRecord(); mListener.onTestEnd(mDataRecord, mTest2Desc); verify(mPerfettoHelper, times(3)).stopCollecting(anyLong(), anyString()); @@ -381,4 +382,27 @@ public class PerfettoListenerTest { assertEquals(1, (int) mInvocationCount.get(mListener.getTestFileName(mTest2Desc))); } + + /* + * Verify perfetto start and stop collection methods called when the text + * proto config option is enabled + */ + @Test + public void testPerfettoSuccessFlowWithTextConfig() throws Exception { + Bundle b = new Bundle(); + b.putString(PerfettoListener.PERFETTO_CONFIG_TEXT_PROTO, "true"); + mListener = initListener(b); + doReturn(true).when(mPerfettoHelper).startCollecting(anyString(), anyBoolean()); + doReturn(true).when(mPerfettoHelper).stopCollecting(anyLong(), anyString()); + // Test run start behavior + mListener.testRunStarted(mRunDesc); + + // Test test start behavior + mListener.testStarted(mTest1Desc); + verify(mPerfettoHelper, times(1)).startCollecting(anyString(), anyBoolean()); + mListener.onTestEnd(mDataRecord, mTest1Desc); + verify(mPerfettoHelper, times(1)).stopCollecting(anyLong(), anyString()); + + } + } diff --git a/libraries/device-collectors/src/test/java/android/device/collectors/RssSnapshotListenerTest.java b/libraries/device-collectors/src/test/java/android/device/collectors/RssSnapshotListenerTest.java new file mode 100644 index 000000000..883a0588b --- /dev/null +++ b/libraries/device-collectors/src/test/java/android/device/collectors/RssSnapshotListenerTest.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.device.collectors; + +import static android.device.collectors.RssSnapshotListener.DROP_CACHE_KEY; +import static android.device.collectors.RssSnapshotListener.OUTPUT_DIR_KEY; +import static android.device.collectors.RssSnapshotListener.PROCESS_NAMES_KEY; +import static android.device.collectors.RssSnapshotListener.PROCESS_SEPARATOR; +import static org.mockito.Mockito.verify; + +import android.app.Instrumentation; +import android.os.Bundle; +import androidx.test.runner.AndroidJUnit4; +import com.android.helpers.RssSnapshotHelper; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.Description; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Android Unit tests for {@link RssSnapshotListener}. + * + * To run: + * atest CollectorDeviceLibTest:android.device.collectors.RssSnapshotListenerTest + */ +@RunWith(AndroidJUnit4.class) +public class RssSnapshotListenerTest { + @Mock private Instrumentation mInstrumentation; + @Mock private RssSnapshotHelper mRssSnapshotHelper; + + private RssSnapshotListener mListener; + private Description mRunDesc; + + private static final String VALID_OUTPUT_DIR = "/data/local/tmp"; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mRunDesc = Description.createSuiteDescription("run"); + } + + private RssSnapshotListener initListener(Bundle b) { + RssSnapshotListener listener = new RssSnapshotListener(b, mRssSnapshotHelper); + listener.setInstrumentation(mInstrumentation); + return listener; + } + + @Test + public void testHelperReceivesProcessNames() throws Exception { + Bundle b = new Bundle(); + b.putString(PROCESS_NAMES_KEY, "process1" + PROCESS_SEPARATOR + "process2"); + b.putString(OUTPUT_DIR_KEY, VALID_OUTPUT_DIR); + + mListener = initListener(b); + + mListener.testRunStarted(mRunDesc); + + verify(mRssSnapshotHelper).setUp(VALID_OUTPUT_DIR, "process1", "process2"); + } + + @Test + public void testAdditionalOptions() throws Exception { + Bundle b = new Bundle(); + b.putString(PROCESS_NAMES_KEY, "process1"); + b.putString(OUTPUT_DIR_KEY, VALID_OUTPUT_DIR); + b.putString(DROP_CACHE_KEY, "all"); + mListener = initListener(b); + + mListener.testRunStarted(mRunDesc); + + verify(mRssSnapshotHelper).setUp(VALID_OUTPUT_DIR, "process1"); + // DROP_CACHE_KEY values: "pagecache" = 1, "slab" = 2, "all" = 3 + verify(mRssSnapshotHelper).setDropCacheOption(3); + } +} diff --git a/libraries/device-collectors/src/test/java/android/device/collectors/SfStatsListenerTest.java b/libraries/device-collectors/src/test/java/android/device/collectors/SfStatsListenerTest.java new file mode 100644 index 000000000..68769f48a --- /dev/null +++ b/libraries/device-collectors/src/test/java/android/device/collectors/SfStatsListenerTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.device.collectors; + +import android.app.Instrumentation; +import android.os.Bundle; +import androidx.test.runner.AndroidJUnit4; + +import com.android.helpers.SfStatsCollectionHelper; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.Description; +import org.junit.runner.Result; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** Unit tests for {@link SfStatsListener} specific behavior. */ +@RunWith(AndroidJUnit4.class) +public final class SfStatsListenerTest { + + // A {@code Description} to pass when faking a test run start call. + private static final Description RUN_DESCRIPTION = Description.createSuiteDescription("run"); + private static final Description TEST_DESCRIPTION = + Description.createTestDescription("run", "test"); + + @Mock private SfStatsCollectionHelper mHelper; + @Mock private Instrumentation mInstrumentation; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testCollect_allProcesses() throws Exception { + SfStatsListener collector = new SfStatsListener(new Bundle(), mHelper); + collector.setInstrumentation(mInstrumentation); + collector.testRunStarted(RUN_DESCRIPTION); + collector.testStarted(TEST_DESCRIPTION); + collector.testFinished(TEST_DESCRIPTION); + collector.testRunFinished(new Result()); + } +} diff --git a/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/ScheduledScenarioRunner.java b/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/ScheduledScenarioRunner.java index d54d24128..ffd54fee4 100644 --- a/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/ScheduledScenarioRunner.java +++ b/libraries/health/runners/longevity/platform/src/android/platform/test/longevity/ScheduledScenarioRunner.java @@ -57,7 +57,10 @@ public class ScheduledScenarioRunner extends LongevityClassRunner { // Please note that in most cases (when the CUJ does not time out) the actual cushion for // teardown is double the value below, as a cushion needs to be created inside the timeout // rule and also outside of it. - @VisibleForTesting static final long TEARDOWN_LEEWAY_MS = 2000; + // This parameter is configurable via the command line as the teardown time varies across CUJs. + @VisibleForTesting static final String TEARDOWN_LEEWAY_OPTION = "teardown-window_ms"; + @VisibleForTesting static final long TEARDOWN_LEEWAY_DEFAULT = 3000L; + private long mTeardownLeewayMs = TEARDOWN_LEEWAY_DEFAULT; private static final String LOG_TAG = ScheduledScenarioRunner.class.getSimpleName(); @@ -86,9 +89,13 @@ public class ScheduledScenarioRunner extends LongevityClassRunner { mTotalTimeoutMs = max(timeout, 0); // Ensure that the enforced timeout is non-negative. This cushion is built in so that the // CUJ still has time for teardown steps when the test portion times out. - mEnforcedTimeoutMs = max(mTotalTimeoutMs - TEARDOWN_LEEWAY_MS, 0); + mEnforcedTimeoutMs = max(mTotalTimeoutMs - mTeardownLeewayMs, 0); mShouldIdle = shouldIdle; mArguments = arguments; + mTeardownLeewayMs = + Long.parseLong( + arguments.getString( + TEARDOWN_LEEWAY_OPTION, String.valueOf(mTeardownLeewayMs))); } @Override @@ -109,9 +116,9 @@ public class ScheduledScenarioRunner extends LongevityClassRunner { // Run the underlying test and report exceptions. statement.evaluate(); } finally { - // If there is time left for idling (i.e. more than TEARDOWN_LEEWAY_MS), + // If there is time left for idling (i.e. more than mTeardownLeewayMs), // and the scenario is set to stay in app, idle for the remainder of - // its timeout window until TEARDOWN_LEEWAY_MS before the start time of + // its timeout window until mTeardownLeewayMs before the start time of // the next scenario, before executing the scenario's @After methods. // The above does not apply if current scenario is the last one, in // which case the idle is never performed regardless of its after_test @@ -125,7 +132,7 @@ public class ScheduledScenarioRunner extends LongevityClassRunner { performIdleBeforeTeardown( max( getTimeRemainingForTimeoutRule() - - TEARDOWN_LEEWAY_MS, + - mTeardownLeewayMs, 0)); } } @@ -254,4 +261,10 @@ public class ScheduledScenarioRunner extends LongevityClassRunner { context.unregisterReceiver(receiver); } } + + /** Expose the teardown leeway since tests rely on it for verifying timing. */ + @VisibleForTesting + long getTeardownLeeway() { + return mTeardownLeewayMs; + } } diff --git a/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/ProfileSuiteTest.java b/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/ProfileSuiteTest.java index 14d66de37..8d7237d3f 100644 --- a/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/ProfileSuiteTest.java +++ b/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/ProfileSuiteTest.java @@ -291,7 +291,8 @@ public class ProfileSuiteTest { long expectedTimeout = suiteTimeoutMsecs - TimeUnit.SECONDS.toMillis(4) - - ScheduledScenarioRunner.TEARDOWN_LEEWAY_MS; + - ScheduledScenarioRunner + .TEARDOWN_LEEWAY_DEFAULT; return abs(exceptionTimeout - expectedTimeout) <= SCHEDULE_LEEWAY_MS; }); diff --git a/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/ScheduledScenarioRunnerTest.java b/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/ScheduledScenarioRunnerTest.java index d36cc36ff..78bc467c1 100644 --- a/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/ScheduledScenarioRunnerTest.java +++ b/libraries/health/runners/longevity/platform/tests/src/android/platform/test/longevity/ScheduledScenarioRunnerTest.java @@ -141,8 +141,7 @@ public class ScheduledScenarioRunnerTest { exception .getTimeUnit() .toMillis(exception.getTimeout()); - long expectedTimeout = - timeoutMs - ScheduledScenarioRunner.TEARDOWN_LEEWAY_MS; + long expectedTimeout = timeoutMs - runner.getTeardownLeeway(); return abs(exceptionTimeout - expectedTimeout) <= TIMING_LEEWAY_MS; }); @@ -196,8 +195,7 @@ public class ScheduledScenarioRunnerTest { // the leeway set in @{link ScheduledScenarioRunner}. verify(runner, times(1)) .performIdleBeforeNextScenario( - getWithinMarginMatcher( - ScheduledScenarioRunner.TEARDOWN_LEEWAY_MS, TIMING_LEEWAY_MS)); + getWithinMarginMatcher(runner.getTeardownLeeway(), TIMING_LEEWAY_MS)); } /** Test that a test set to stay in the app after the test idles after its @Test method. */ @@ -226,8 +224,7 @@ public class ScheduledScenarioRunnerTest { verify(runner, times(1)) .performIdleBeforeTeardown( getWithinMarginMatcher( - timeoutMs - 2 * ScheduledScenarioRunner.TEARDOWN_LEEWAY_MS, - TIMING_LEEWAY_MS)); + timeoutMs - 2 * runner.getTeardownLeeway(), TIMING_LEEWAY_MS)); // Test should have passed. verify(mRunNotifier, never()).fireTestFailure(any(Failure.class)); } @@ -389,6 +386,23 @@ public class ScheduledScenarioRunnerTest { Assert.assertTrue(abs(actualSleepDuration - expectedSleepMillis) <= TIMING_LEEWAY_MS); } + /** Test that the teardown leeway override works. */ + @Test + public void testTeardownLeewayOverride() throws Throwable { + Bundle args = new Bundle(); + long leewayOverride = 1000L; + args.putString( + ScheduledScenarioRunner.TEARDOWN_LEEWAY_OPTION, String.valueOf(leewayOverride)); + ScheduledScenarioRunner runner = + new ScheduledScenarioRunner( + ArgumentTest.class, + Scenario.newBuilder().build(), + TimeUnit.SECONDS.toMillis(6), + false, + args); + Assert.assertEquals(leewayOverride, runner.getTeardownLeeway()); + } + /** * Helper method to get an argument matcher that checks whether the input value is equal to * expected value within a margin. diff --git a/libraries/launcher-helper/src/android/support/test/launcherhelper/AutoLauncherStrategy.java b/libraries/launcher-helper/src/android/support/test/launcherhelper/AutoLauncherStrategy.java index c547f0d7f..869d87019 100644 --- a/libraries/launcher-helper/src/android/support/test/launcherhelper/AutoLauncherStrategy.java +++ b/libraries/launcher-helper/src/android/support/test/launcherhelper/AutoLauncherStrategy.java @@ -23,14 +23,14 @@ import android.support.test.uiautomator.Direction; import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.UiObject2; import android.support.test.uiautomator.Until; +import android.system.helpers.CommandsHelper; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.regex.Pattern; -import java.util.stream.Stream; import java.util.stream.Collectors; - +import java.util.stream.Stream; public class AutoLauncherStrategy implements IAutoLauncherStrategy { private static final String LOG_TAG = AutoLauncherStrategy.class.getSimpleName(); @@ -88,6 +88,7 @@ public class AutoLauncherStrategy implements IAutoLauncherStrategy { protected UiDevice mDevice; private Instrumentation mInstrumentation; + private CommandsHelper mCommandsHelper; /** * {@inheritDoc} @@ -111,6 +112,7 @@ public class AutoLauncherStrategy implements IAutoLauncherStrategy { @Override public void setInstrumentation(Instrumentation instrumentation) { mInstrumentation = instrumentation; + mCommandsHelper = CommandsHelper.getInstance(mInstrumentation); } /** @@ -201,9 +203,21 @@ public class AutoLauncherStrategy implements IAutoLauncherStrategy { openFacet("Notification"); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ + @Override + public void openNotifications() { + String cmd = "cmd statusbar expand-notifications"; + mCommandsHelper.executeShellCommand(cmd); + } + + /** {@inheritDoc} */ + @Override + public void pressHome() { + String cmd = "input keyevent KEYCODE_HOME"; + mCommandsHelper.executeShellCommand(cmd); + } + + /** {@inheritDoc} */ @Override public void openAssistantFacet() { openFacet("Google Assistant"); @@ -326,6 +340,18 @@ public class AutoLauncherStrategy implements IAutoLauncherStrategy { } } + @Override + public void openBluetoothAudioApp() { + String appName = "Bluetooth Audio"; + if (checkApplicationExists(appName)) { + UiObject2 app = mDevice.findObject(By.clickable(true).hasDescendant(By.text(appName))); + app.clickAndWait(Until.newWindow(), APP_LAUNCH_TIMEOUT); + mDevice.waitForIdle(); + } else { + throw new RuntimeException(String.format("Application %s not found", appName)); + } + } + private UiObject2 findApplication(String appName) { BySelector appSelector = By.clickable(true).hasDescendant(By.text(appName)); if (mDevice.hasObject(SCROLLABLE_APP_LIST)) { diff --git a/libraries/launcher-helper/src/android/support/test/launcherhelper/IAutoLauncherStrategy.java b/libraries/launcher-helper/src/android/support/test/launcherhelper/IAutoLauncherStrategy.java index 8351b82a6..731d39cfa 100644 --- a/libraries/launcher-helper/src/android/support/test/launcherhelper/IAutoLauncherStrategy.java +++ b/libraries/launcher-helper/src/android/support/test/launcherhelper/IAutoLauncherStrategy.java @@ -65,9 +65,10 @@ public interface IAutoLauncherStrategy extends ILauncherStrategy { */ void openAssistantFacet(); - /** - * Open Quick Settings. - */ + /** This method is to open Bluetooth Audio application */ + void openBluetoothAudioApp(); + + /** Open Quick Settings. */ void openQuickSettings(); /** @@ -95,4 +96,10 @@ public interface IAutoLauncherStrategy extends ILauncherStrategy { * @param appName application to be opened. */ void openApp(String appName); + + /** This method is to open notifications */ + void openNotifications(); + + /** This method is to navigate to device home */ + void pressHome(); } diff --git a/libraries/launcher-helper/src/android/support/test/launcherhelper/LauncherStrategyFactory.java b/libraries/launcher-helper/src/android/support/test/launcherhelper/LauncherStrategyFactory.java index f812b991f..8a3db419d 100644 --- a/libraries/launcher-helper/src/android/support/test/launcherhelper/LauncherStrategyFactory.java +++ b/libraries/launcher-helper/src/android/support/test/launcherhelper/LauncherStrategyFactory.java @@ -42,6 +42,7 @@ public class LauncherStrategyFactory { mKnownLauncherStrategies = new HashSet<>(); registerLauncherStrategy(AospLauncherStrategy.class); registerLauncherStrategy(AutoLauncherStrategy.class); + registerLauncherStrategy(VolvoLauncherStrategy.class); registerLauncherStrategy(GoogleExperienceLauncherStrategy.class); registerLauncherStrategy(Launcher3Strategy.class); registerLauncherStrategy(NexusLauncherStrategy.class); diff --git a/libraries/launcher-helper/src/android/support/test/launcherhelper/VolvoLauncherStrategy.java b/libraries/launcher-helper/src/android/support/test/launcherhelper/VolvoLauncherStrategy.java new file mode 100644 index 000000000..695a9b26c --- /dev/null +++ b/libraries/launcher-helper/src/android/support/test/launcherhelper/VolvoLauncherStrategy.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.support.test.launcherhelper; + +import android.app.Instrumentation; +import android.os.SystemClock; +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.BySelector; +import android.support.test.uiautomator.UiDevice; +import android.support.test.uiautomator.UiObject2; +import android.support.test.uiautomator.Until; + +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** Implementation of {@link ILauncherStrategy} to support Volvo launcher */ +public class VolvoLauncherStrategy extends AutoLauncherStrategy { + private static final String VOLVO_LAUNCHER_PACKAGE = "com.volvocars.launcher"; + private static final String SYSTEM_UI_PACKAGE = "com.android.systemui"; + + private static final Map<String, BySelector> FACET_MAP = + Stream.of( + new Object[][] { + { + "App Grid", + By.res(SYSTEM_UI_PACKAGE, "nav_bar_apps").clickable(true) + }, + }) + .collect( + Collectors.toMap( + data -> (String) data[0], data -> (BySelector) data[1])); + + private static final Map<String, BySelector> APP_OPEN_VERIFIERS = + Stream.of( + new Object[][] { + {"App Grid", By.res(VOLVO_LAUNCHER_PACKAGE, "apps_pane")}, + }) + .collect( + Collectors.toMap( + data -> (String) data[0], data -> (BySelector) data[1])); + + private static final long APP_LAUNCH_TIMEOUT_MS = 10000; + private static final long UI_WAIT_TIMEOUT_MS = 5000; + private static final long POLL_INTERVAL = 100; + + protected UiDevice mDevice; + private Instrumentation mInstrumentation; + + @Override + public String getSupportedLauncherPackage() { + return VOLVO_LAUNCHER_PACKAGE; + } + + @Override + public void setUiDevice(UiDevice uiDevice) { + mDevice = uiDevice; + } + + @Override + public void setInstrumentation(Instrumentation instrumentation) { + super.setInstrumentation(instrumentation); + mInstrumentation = instrumentation; + } + + @Override + public void openApp(String appName) { + if (checkApplicationExists(appName)) { + UiObject2 app = mDevice.findObject(By.clickable(true).hasDescendant(By.text(appName))); + app.clickAndWait(Until.newWindow(), APP_LAUNCH_TIMEOUT_MS); + mDevice.waitForIdle(); + } else { + throw new RuntimeException(String.format("Application %s not found", appName)); + } + } + + @Override + public void openBluetoothAudioApp() { + String appName = "Bluetooth Media Player"; + if (checkApplicationExists(appName)) { + UiObject2 app = mDevice.findObject(By.clickable(true).hasDescendant(By.text(appName))); + app.clickAndWait(Until.newWindow(), APP_LAUNCH_TIMEOUT_MS); + mDevice.waitForIdle(); + } else { + throw new RuntimeException(String.format("Application %s not found", appName)); + } + } + + @Override + public boolean checkApplicationExists(String appName) { + openAppGridFacet(); + UiObject2 app = findApplication(appName); + return app != null; + } + + @Override + public void openAppGridFacet() { + openFacet("App Grid"); + } + + @Override + public void openMapsFacet() { + // Volvo does not have Facet for Maps, so open Maps from App Grid + openApp("Maps"); + } + + private void openFacet(String facetName) { + BySelector facetSelector = FACET_MAP.get(facetName); + UiObject2 facet = mDevice.findObject(facetSelector); + if (facet != null) { + facet.click(); + waitUntilAppOpen(facetName, APP_LAUNCH_TIMEOUT_MS); + } else { + throw new RuntimeException(String.format("Failed to find %s facet.", facetName)); + } + } + + private void waitUntilAppOpen(String appName, long timeout) { + SystemClock.sleep(timeout); + long startTime = SystemClock.uptimeMillis(); + boolean isOpen = false; + do { + isOpen = mDevice.hasObject(APP_OPEN_VERIFIERS.get(appName)); + if (isOpen) { + break; + } + SystemClock.sleep(POLL_INTERVAL); + } while ((SystemClock.uptimeMillis() - startTime) < timeout); + if (!isOpen) { + throw new IllegalStateException( + String.format( + "Did not find any app of %s in foreground after %d ms.", + appName, timeout)); + } + } + + private UiObject2 findApplication(String appName) { + BySelector appSelector = By.clickable(true).hasDescendant(By.text(appName)); + return mDevice.findObject(appSelector); + } +} diff --git a/scripts/perfetto-setup/Android.mk b/scripts/perfetto-setup/Android.mk new file mode 100644 index 000000000..a742eea8d --- /dev/null +++ b/scripts/perfetto-setup/Android.mk @@ -0,0 +1,48 @@ +# +# Copyright (C) 2019 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := trace_config_detailed.textproto +LOCAL_MODULE_CLASS := ETC +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/local/tmp +LOCAL_PREBUILT_MODULE_FILE := prebuilts/tools/linux-x86_64/perfetto/configs/trace_config_detailed.textproto +include $(BUILD_PREBUILT) + +include $(CLEAR_VARS) +LOCAL_MODULE := long_trace_config.textproto +LOCAL_MODULE_CLASS := ETC +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/local/tmp +LOCAL_PREBUILT_MODULE_FILE := prebuilts/tools/linux-x86_64/perfetto/configs/long_trace_config.textproto +include $(BUILD_PREBUILT) + +include $(CLEAR_VARS) +LOCAL_MODULE := trace_config.textproto +LOCAL_MODULE_CLASS := ETC +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/local/tmp +LOCAL_PREBUILT_MODULE_FILE := prebuilts/tools/linux-x86_64/perfetto/configs/trace_config.textproto +include $(BUILD_PREBUILT) + +include $(CLEAR_VARS) +LOCAL_MODULE := trace_config_experimental.textproto +LOCAL_MODULE_CLASS := ETC +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/local/tmp +LOCAL_PREBUILT_MODULE_FILE := prebuilts/tools/linux-x86_64/perfetto/configs/trace_config_experimental.textproto +include $(BUILD_PREBUILT) + diff --git a/tests/health/scenarios/src/android/platform/test/scenario/sleep/Idle.java b/tests/health/scenarios/src/android/platform/test/scenario/sleep/Idle.java index 882523c1c..4efbd071f 100644 --- a/tests/health/scenarios/src/android/platform/test/scenario/sleep/Idle.java +++ b/tests/health/scenarios/src/android/platform/test/scenario/sleep/Idle.java @@ -18,8 +18,10 @@ package android.platform.test.scenario.sleep; import android.os.SystemClock; import android.platform.test.option.LongOption; +import android.platform.test.rule.NaturalOrientationRule; import android.platform.test.scenario.annotation.Scenario; +import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -31,6 +33,9 @@ import org.junit.runners.JUnit4; @Scenario @RunWith(JUnit4.class) public class Idle { + // Class-level rules + @ClassRule public static NaturalOrientationRule orientationRule = new NaturalOrientationRule(); + @Rule public final LongOption mDurationMs = new LongOption("durationMs").setDefault(1000L); @Test diff --git a/tests/health/scenarios/src/android/platform/test/scenario/system/ScreenOff.java b/tests/health/scenarios/src/android/platform/test/scenario/system/ScreenOff.java index 1f5e23475..a2e1290c4 100644 --- a/tests/health/scenarios/src/android/platform/test/scenario/system/ScreenOff.java +++ b/tests/health/scenarios/src/android/platform/test/scenario/system/ScreenOff.java @@ -57,8 +57,11 @@ public class ScreenOff { @After public void tearDown() throws RemoteException { if (mTurnScreenBackOn.get()) { - mDevice.wakeUp(); + // Wake up the display. wakeUp() is not used here as when the duration is short, the + // device might register a double power button press and launch camera. + mDevice.pressMenu(); mDevice.waitForIdle(); + // Unlock the screen. mDevice.pressMenu(); mDevice.waitForIdle(); } |