diff options
author | Allen Hair <allenhair@google.com> | 2015-03-06 18:43:33 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2015-03-06 18:43:33 +0000 |
commit | 33fe33ac4e56cadcc977a23b1d3ed5517986bbe3 (patch) | |
tree | 83f06c55968f45ee14f8f6124d081a1d637f2207 | |
parent | 63a15ec1c8fa778107cd33d7ddddd1e3ec12294e (diff) | |
parent | c0c4c33fce95ddc694f2231ffbc87b8de04485a5 (diff) | |
download | uiautomator-33fe33ac4e56cadcc977a23b1d3ed5517986bbe3.tar.gz |
Merge "Add support for dumpsys gfxinfo based jank detection."
13 files changed, 589 insertions, 211 deletions
diff --git a/jank/src/android/support/test/jank/GfxMonitor.java b/jank/src/android/support/test/jank/GfxMonitor.java new file mode 100644 index 0000000..36ca81b --- /dev/null +++ b/jank/src/android/support/test/jank/GfxMonitor.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2015 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.jank; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** Annotation used to configure a gfx monitor. */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface GfxMonitor { + /** The name of the process to monitor */ + String processName(); +} diff --git a/jank/src/android/support/test/jank/JankTest.java b/jank/src/android/support/test/jank/JankTest.java index 45a386b..c79c6fa 100644 --- a/jank/src/android/support/test/jank/JankTest.java +++ b/jank/src/android/support/test/jank/JankTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2015 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. @@ -26,9 +26,6 @@ import java.lang.annotation.Target; @Target(ElementType.METHOD) public @interface JankTest { - /** The type of jank to measure */ - JankType type(); - /** The minimum number of frames expected */ int expectedFrames(); @@ -59,8 +56,10 @@ public @interface JankTest { /** * Alternate method to execute after all iterations have completed. * <p> - * <b>Important:</b> the annotated method must take a parameter of type {@link JankMetrics}. - * See {@link JankTestBase#afterTest(JankMetrics)} - * */ + * <b>Important:</b> the annotated method must take a parameter of type + * {@link android.os.Bundle}.</p> + * + * @see JankTestBase#afterTest(android.os.Bundle) + */ String afterTest() default "afterTest"; } diff --git a/jank/src/android/support/test/jank/JankTestBase.java b/jank/src/android/support/test/jank/JankTestBase.java index fd85fab..1439da0 100644 --- a/jank/src/android/support/test/jank/JankTestBase.java +++ b/jank/src/android/support/test/jank/JankTestBase.java @@ -20,12 +20,15 @@ import android.app.Activity; import android.app.Instrumentation; import android.os.Bundle; import android.support.test.InstrumentationRegistry; +import android.support.test.jank.internal.JankMonitorFactory; +import android.support.test.jank.internal.JankMonitor; import android.support.test.runner.AndroidJUnitRunner; import android.test.InstrumentationTestCase; import android.test.InstrumentationTestRunner; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.List; /** * Base test class for measuring Jank. @@ -70,14 +73,9 @@ public class JankTestBase extends InstrumentationTestCase { * <p>Note: default implementation reports the aggregated jank metrics via * {@link Instrumentation#sendStatus(int, Bundle)} * @param metrics the aggregated jank metrics after looped execution - * */ - public void afterTest(JankMetrics metrics) throws Exception { - Bundle status = new Bundle(); - status.putDouble(KEY_AVG_JANK, metrics.averageJank); - status.putInt(KEY_MAX_JANK, metrics.maxJank); - status.putDouble(KEY_AVG_FPS, metrics.averageFps); - status.putDouble(KEY_AVG_MAX_FRAME_DURATION, metrics.averageMaxFrameDuration); - getInstrumentation().sendStatus(Activity.RESULT_OK, status); + */ + public void afterTest(Bundle metrics) { + getInstrumentation().sendStatus(Activity.RESULT_OK, metrics); } /** Return the index of the currently executing iteration. */ @@ -96,14 +94,10 @@ public class JankTestBase extends InstrumentationTestCase { Method afterLoop = resolveMethod(annotation.afterLoop()); Method afterTest = resolveAfterTest(annotation.afterTest()); - // Get a JankUtil instance - JankUtil jank = JankUtil.getInstance(getInstrumentation()); - - // Stats to track - int sumJankyFrames = 0; - int maxJankyFrames = 0; - double sumFps = 0.0f; - double sumLongestFrame = 0.0f; + // Get the appropriate JankMonitors for the test type + JankMonitorFactory factory = new JankMonitorFactory(getInstrumentation().getUiAutomation()); + List<JankMonitor> monitors = factory.getJankMonitors(testMethod); + assertTrue("No monitors configured for this test", monitors.size() > 0); // Test setup beforeTest.invoke(this, (Object[])null); @@ -116,36 +110,32 @@ public class JankTestBase extends InstrumentationTestCase { beforeLoop.invoke(this, (Object[])null); // Start monitoring jank - jank.startMonitor(annotation.type()); + for (JankMonitor monitor : monitors) { + monitor.startIteration(); + } // Run the test method testMethod.invoke(this, (Object[])null); // Stop monitoring - JankResult result = jank.stopMonitor(); + for (JankMonitor monitor : monitors) { + int numFrames = monitor.stopIteration(); - // Fail the test if we didn't get enough frames - assertTrue(String.format("Too few frames received. Expected: %d, Received: %d.", - annotation.expectedFrames(), result.numFrames), - result.numFrames >= annotation.expectedFrames()); - - // Update stats - sumJankyFrames += result.numJanky; - maxJankyFrames = Math.max(maxJankyFrames, result.numJanky); - sumFps += result.fps; - sumLongestFrame += result.longestFrameNormalized; + // Fail the test if we didn't get enough frames + assertTrue(String.format("Too few frames received. Expected: %d, Received: %d.", + annotation.expectedFrames(), numFrames), + numFrames >= annotation.expectedFrames()); + } // Loop tear down afterLoop.invoke(this, (Object[])null); } // Report aggregated results - JankMetrics metrics = new JankMetrics(); - metrics.averageJank = (double)sumJankyFrames / iterations; - metrics.maxJank = maxJankyFrames; - metrics.averageFps = sumFps / iterations; - metrics.averageMaxFrameDuration = sumLongestFrame / iterations; - // Test tear down and reporting + Bundle metrics = new Bundle(); + for (JankMonitor monitor : monitors) { + metrics.putAll(monitor.getMetrics()); + } afterTest.invoke(this, metrics); } @@ -177,7 +167,7 @@ public class JankTestBase extends InstrumentationTestCase { Method method = null; try { - method = getClass().getMethod(name, JankMetrics.class); + method = getClass().getMethod(name, Bundle.class); } catch (NoSuchMethodException e) { fail("method annotated with JankTest#afterTest has wrong signature"); } diff --git a/jank/src/android/support/test/jank/JankUtil.java b/jank/src/android/support/test/jank/JankUtil.java deleted file mode 100644 index d433d98..0000000 --- a/jank/src/android/support/test/jank/JankUtil.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2014 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.jank; - -import android.accessibilityservice.AccessibilityServiceInfo; -import android.app.Instrumentation; -import android.app.UiAutomation; -import android.util.Log; -import android.view.FrameStats; -import android.view.accessibility.AccessibilityWindowInfo; - -/** The {@link JankUtil} class provides functionality for monitoring jank. */ -public class JankUtil { - - private static final String TAG = JankUtil.class.getSimpleName(); - - // Singleton instance - private static JankUtil sInstance; - - private UiAutomation mUiAutomation; - private JankMonitor mMonitor; - - /** Private constructor. Clients should use {@link JankUtil#getInstance(Instrumentation)}. */ - private JankUtil(UiAutomation automation) { - mUiAutomation = automation; - - // Subscribe to window information - AccessibilityServiceInfo info = mUiAutomation.getServiceInfo(); - info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS; - mUiAutomation.setServiceInfo(info); - } - - /** Returns a {@link JankUtil} instance. */ - public static JankUtil getInstance(Instrumentation instrumentation) { - if (sInstance == null) { - sInstance = new JankUtil(instrumentation.getUiAutomation()); - } - return sInstance; - } - - /** Starts monitoring for janky frames of the given {@code type}. */ - public void startMonitor(JankType type) { - if (mMonitor != null) { - throw new IllegalStateException("Monitor already started"); - } - - if (type == JankType.CONTENT_FRAMES) { - mMonitor = new WindowContentJankMonitor(); - } else if (type == JankType.ANIMATION_FRAMES) { - mMonitor = new WindowAnimationJankMonitor(); - } else { - throw new RuntimeException("Invalid type"); - } - - mMonitor.clear(); - } - - /** Stops monitoring and returns the {@link JankResult} for this monitoring session. */ - public JankResult stopMonitor() { - FrameStats stats = mMonitor.getStats(); - mMonitor = null; - return JankResult.analyze(stats); - } - - /** Returns the id of the current application window. */ - private int getCurrentWindow() { - for (AccessibilityWindowInfo window : mUiAutomation.getWindows()) { - if (window.getType() == AccessibilityWindowInfo.TYPE_APPLICATION) { - return window.getId(); - } - } - throw new RuntimeException("No application window found"); - } - - /** Generic monitoring interface */ - private static interface JankMonitor { - public void clear(); - public FrameStats getStats(); - } - - /** Monitor for detecting window content frame jank. */ - private class WindowContentJankMonitor implements JankMonitor { - private int mWindowId = -1; - - @Override - public void clear() { - mWindowId = getCurrentWindow(); - mUiAutomation.clearWindowContentFrameStats(mWindowId); - } - - @Override - public FrameStats getStats() { - int currentWindow = getCurrentWindow(); - if (currentWindow != mWindowId) { - Log.w(TAG, "Current window changed during the test. Did you mean to measure " + - "ANIMATION_FRAMES?"); - } - mWindowId = -1; - return mUiAutomation.getWindowContentFrameStats(currentWindow); - } - } - - /** Monitor for detecting window animation frame jank. */ - private class WindowAnimationJankMonitor implements JankMonitor { - @Override - public void clear() { - mUiAutomation.clearWindowAnimationFrameStats(); - } - - @Override - public FrameStats getStats() { - return mUiAutomation.getWindowAnimationFrameStats(); - } - } -} diff --git a/jank/src/android/support/test/jank/WindowAnimationFrameStatsMonitor.java b/jank/src/android/support/test/jank/WindowAnimationFrameStatsMonitor.java new file mode 100644 index 0000000..1f7ab57 --- /dev/null +++ b/jank/src/android/support/test/jank/WindowAnimationFrameStatsMonitor.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2015 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.jank; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** Annotation used to configure a window animation frame monitor. */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface WindowAnimationFrameStatsMonitor { +} diff --git a/jank/src/android/support/test/jank/JankMetrics.java b/jank/src/android/support/test/jank/WindowContentFrameStatsMonitor.java index b227804..034eec7 100644 --- a/jank/src/android/support/test/jank/JankMetrics.java +++ b/jank/src/android/support/test/jank/WindowContentFrameStatsMonitor.java @@ -16,17 +16,13 @@ package android.support.test.jank; -/** - * {@link JankMetrics} contains aggregated {@link JankResult} after looped execution of a test case. - * - */ -public class JankMetrics { - /** average number of jank across iterations */ - public double averageJank; - /** max jank number among all iterations */ - public int maxJank; - /** average FPS across iterations */ - public double averageFps; - /** average of longest frame duration reported from all frames rendered for each iteration */ - public double averageMaxFrameDuration; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** Annotation used to configure a window content frame monitor. */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface WindowContentFrameStatsMonitor { } diff --git a/jank/src/android/support/test/jank/JankResult.java b/jank/src/android/support/test/jank/internal/FrameStatsMonitorBase.java index 71e595c..162af3a 100644 --- a/jank/src/android/support/test/jank/JankResult.java +++ b/jank/src/android/support/test/jank/internal/FrameStatsMonitorBase.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2015 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. @@ -14,44 +14,67 @@ * limitations under the License. */ -package android.support.test.jank; +package android.support.test.jank.internal; +import android.os.Bundle; import android.util.Log; import android.view.FrameStats; -/** A {@link JankResult} contains the results of a jank monitoring session. This includes the number - * of frames analyzed, the number of frames that were janky, the average frames per second, as well - * as the nomalized longest frame time.*/ -public class JankResult { +import java.util.ArrayList; - private static final String TAG = JankResult.class.getSimpleName(); +/** + * Abstract base class for {@link android.view.FrameStats} based {@link JankMonitor}s. + * + * Reports average and max jank, as well as average frames per second and the longest normalized + * frame time. + */ +abstract class FrameStatsMonitorBase implements JankMonitor { + + private static final String TAG = "JankTestHelper"; // Maximum normalized error in frame duration before the frame is considered janky private static final double MAX_ERROR = 0.5f; + // Maximum normalized frame duration before the frame is considered a pause private static final double PAUSE_THRESHOLD = 15.0f; - public final int numFrames; - public final int numJanky; - public final double fps; - public final double longestFrameNormalized; - - /** Private constructor. Clients should use {@link JankResult#analyze(FrameStats)} instead. */ - private JankResult(int numFrames, int numJanky, double fps, double longestFrameNormalized) { - this.numFrames = numFrames; - this.numJanky = numJanky; - this.fps = fps; - this.longestFrameNormalized = longestFrameNormalized; + // Jank metrics namespace and helper class + private static final String MONITOR_PREFIX = "frame"; + private static final MetricsHelper mMetricsHelper = new MetricsHelper(MONITOR_PREFIX); + + // Key values for the metrics reported by this monitor + private static final String KEY_NUM_JANKY = "jank"; + private static final String KEY_FPS = "fps"; + private static final String KEY_LONGEST_FRAME = "max-frame-duration"; + + // Accumulated stats + ArrayList<Integer> mJankyFrames = new ArrayList<Integer>(); + ArrayList<Double> mFps = new ArrayList<Double>(); + ArrayList<Double> mLongestNormalizedFrames = new ArrayList<Double>(); + + + public Bundle getMetrics() { + Bundle metrics = new Bundle(); + + // Store average and max jank + mMetricsHelper.putSummaryMetrics(metrics, KEY_NUM_JANKY, mJankyFrames); + + // Store average fps + mMetricsHelper.putAverageMetric(metrics, KEY_FPS, mFps); + + // Store average max frame duration + mMetricsHelper.putAverageMetric(metrics, KEY_LONGEST_FRAME, mLongestNormalizedFrames); + + return metrics; } - /** Analyze the given {@link FrameStats} and return the resulting jank info. */ - public static JankResult analyze(FrameStats stats) { + protected void analyze(FrameStats stats) { int frameCount = stats.getFrameCount(); long refreshPeriod = stats.getRefreshPeriodNano(); int numJanky = 0; double longestFrameNormalized = 0.0f; - long totalDuration = 0; + double totalDuration = 0.0f; // Skip first frame for (int i = 2; i < frameCount; i++) { // Handle frames that have not been presented. @@ -74,6 +97,9 @@ public class JankResult { } double fps = (double)(frameCount - 2) / totalDuration * 1000000000; - return new JankResult(frameCount, numJanky, fps, longestFrameNormalized); + // Store metrics from this run + mJankyFrames.add(numJanky); + mFps.add(fps); + mLongestNormalizedFrames.add(longestFrameNormalized); } } diff --git a/jank/src/android/support/test/jank/internal/GfxMonitorImpl.java b/jank/src/android/support/test/jank/internal/GfxMonitorImpl.java new file mode 100644 index 0000000..9aae574 --- /dev/null +++ b/jank/src/android/support/test/jank/internal/GfxMonitorImpl.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2015 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.jank.internal; + +import android.app.UiAutomation; +import android.os.Bundle; +import android.os.ParcelFileDescriptor; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; +import java.util.regex.Matcher; + +import junit.framework.Assert; + +/** + * Monitors dumpsys gfxinfo to detect janky frames. + * + * Reports average and max jank. Additionally reports summary statistics for common problems that + * can lead to dropped frames. + */ +class GfxMonitorImpl implements JankMonitor { + + // Jank metrics namespace and helper class + private static final String MONITOR_PREFIX = "gfx"; + private static final MetricsHelper mMetricsHelper = new MetricsHelper(MONITOR_PREFIX); + + // Key values for the metrics reported by this monitor + private static final String KEY_NUM_JANKY = "jank"; + private static final String KEY_MISSED_VSYNC = "missed-vsync"; + private static final String KEY_HIGH_INPUT_LATENCY = "high-input-latency"; + private static final String KEY_SLOW_UI_THREAD = "slow-ui-thread"; + private static final String KEY_SLOW_BITMAP_UPLOADS = "slow-bitmap-uploads"; + private static final String KEY_SLOW_DRAW = "slow-draw"; + + // Patterns used for parsing dumpsys gfxinfo output + private static final Pattern TOTAL_FRAMES_PATTERN = + Pattern.compile("\\s*Total frames rendered: (\\d+)"); + private static final Pattern JANKY_FRAMES_PATTERN = + Pattern.compile("\\s*Janky frames: (\\d+) \\(.*\\)"); + private static final Pattern MISSED_VSYNC_PATTERN = + Pattern.compile("\\s*Number Missed Vsync: (\\d+)"); + private static final Pattern INPUT_LATENCY_PATTERN = + Pattern.compile("\\s*Number High input latency: (\\d+)"); + private static final Pattern SLOW_UI_PATTERN = + Pattern.compile("\\s*Number Slow UI thread: (\\d+)"); + private static final Pattern SLOW_BITMAP_PATTERN = + Pattern.compile("\\s*Number Slow bitmap uploads: (\\d+)"); + private static final Pattern SLOW_DRAW_PATTERN = + Pattern.compile("\\s*Number Slow draw: (\\d+)"); + + // Used to invoke dumpsys gfxinfo + private UiAutomation mUiAutomation; + private String mProcess; + + // Metrics accumulated for each iteration + private List<Integer> jankyFrames = new ArrayList<Integer>(); + private List<Integer> missedVsync = new ArrayList<Integer>(); + private List<Integer> highInputLatency = new ArrayList<Integer>(); + private List<Integer> slowUiThread = new ArrayList<Integer>(); + private List<Integer> slowBitmapUploads = new ArrayList<Integer>(); + private List<Integer> slowDraw = new ArrayList<Integer>(); + + + public GfxMonitorImpl(UiAutomation automation, String process) { + mUiAutomation = automation; + mProcess = process; + } + + @Override + public void startIteration() throws IOException { + // Clear out any previous data + ParcelFileDescriptor stdout = mUiAutomation.executeShellCommand( + String.format("dumpsys gfxinfo %s reset", mProcess)); + + // Read the output, but don't do anything with it + BufferedReader stream = new BufferedReader(new InputStreamReader( + new ParcelFileDescriptor.AutoCloseInputStream(stdout))); + while (stream.readLine() != null) { + } + } + + @Override + public int stopIteration() throws IOException { + ParcelFileDescriptor stdout = mUiAutomation.executeShellCommand( + String.format("dumpsys gfxinfo %s", mProcess)); + BufferedReader stream = new BufferedReader(new InputStreamReader( + new ParcelFileDescriptor.AutoCloseInputStream(stdout))); + + // Wait until we enter the frame stats section + String line; + while ((line = stream.readLine()) != null) { + if (line.startsWith("Frame stats:")) { + break; + } + } + Assert.assertTrue("Failed to locate frame stats in gfxinfo output", + line != null && line.startsWith("Frame stats:")); + + // The frame stats section has the following output: + // Frame stats: + // Total frames rendered: ### + // Janky frames: ### (##.##%) + // Number Missed Vsync: # + // Number High input latency: # + // Number Slow UI thread: # + // Number Slow bitmap uploads: # + // Number Slow draw: # + + // Get Total Frames + String part; + if ((part = getMatchGroup(stream.readLine(), TOTAL_FRAMES_PATTERN, 1)) == null) { + Assert.fail("Failed to parse total frames"); + } + int totalFrames = Integer.parseInt(part); + + // Get Num Janky + if ((part = getMatchGroup(stream.readLine(), JANKY_FRAMES_PATTERN, 1)) == null) { + Assert.fail("Failed to parse janky frames"); + } + jankyFrames.add(Integer.parseInt(part)); + + // Get Missed Vsync + if ((part = getMatchGroup(stream.readLine(), MISSED_VSYNC_PATTERN, 1)) == null) { + Assert.fail("Failed to parse number missed vsync"); + } + missedVsync.add(Integer.parseInt(part)); + + // Get High input latency + if ((part = getMatchGroup(stream.readLine(), INPUT_LATENCY_PATTERN, 1)) == null) { + Assert.fail("Failed to parse number high input latency"); + } + highInputLatency.add(Integer.parseInt(part)); + + // Get Slow UI thread + if ((part = getMatchGroup(stream.readLine(), SLOW_UI_PATTERN, 1)) == null) { + Assert.fail("Failed to parse number slow ui thread"); + } + slowUiThread.add(Integer.parseInt(part)); + + // Get Slow bitmap uploads + if ((part = getMatchGroup(stream.readLine(), SLOW_BITMAP_PATTERN, 1)) == null) { + Assert.fail("Failed to parse number slow bitmap uploads"); + } + slowBitmapUploads.add(Integer.parseInt(part)); + + // Get Slow draw + if ((part = getMatchGroup(stream.readLine(), SLOW_DRAW_PATTERN, 1)) == null) { + Assert.fail("Failed to parse number slow draw"); + } + slowDraw.add(Integer.parseInt(part)); + + return totalFrames; + } + + public Bundle getMetrics() { + Bundle metrics = new Bundle(); + mMetricsHelper.putSummaryMetrics(metrics, KEY_NUM_JANKY, jankyFrames); + mMetricsHelper.putSummaryMetrics(metrics, KEY_MISSED_VSYNC, missedVsync); + mMetricsHelper.putSummaryMetrics(metrics, KEY_HIGH_INPUT_LATENCY, highInputLatency); + mMetricsHelper.putSummaryMetrics(metrics, KEY_SLOW_UI_THREAD, slowUiThread); + mMetricsHelper.putSummaryMetrics(metrics, KEY_SLOW_BITMAP_UPLOADS, slowBitmapUploads); + mMetricsHelper.putSummaryMetrics(metrics, KEY_SLOW_DRAW, slowDraw); + + return metrics; + } + + private String getMatchGroup(String input, Pattern pattern, int groupIndex) { + String ret = null; + Matcher matcher = pattern.matcher(input); + if (matcher.matches()) { + ret = matcher.group(groupIndex); + } + return ret; + } +} diff --git a/jank/src/android/support/test/jank/JankType.java b/jank/src/android/support/test/jank/internal/JankMonitor.java index b1a0fde..6816f6b 100644 --- a/jank/src/android/support/test/jank/JankType.java +++ b/jank/src/android/support/test/jank/internal/JankMonitor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2015 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. @@ -14,9 +14,14 @@ * limitations under the License. */ -package android.support.test.jank; +package android.support.test.jank.internal; -/** Enumeration used to specify the type of jank to measure. */ -public enum JankType { - CONTENT_FRAMES, ANIMATION_FRAMES +import android.os.Bundle; + +public interface JankMonitor { + public abstract void startIteration() throws Throwable; + + public abstract int stopIteration() throws Throwable; + + public abstract Bundle getMetrics() throws Throwable; } diff --git a/jank/src/android/support/test/jank/internal/JankMonitorFactory.java b/jank/src/android/support/test/jank/internal/JankMonitorFactory.java new file mode 100644 index 0000000..9c25262 --- /dev/null +++ b/jank/src/android/support/test/jank/internal/JankMonitorFactory.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2015 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.jank.internal; + +import android.app.UiAutomation; +import android.support.test.jank.WindowAnimationFrameStatsMonitor; +import android.support.test.jank.WindowContentFrameStatsMonitor; +import android.support.test.jank.GfxMonitor; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +public class JankMonitorFactory { + + private UiAutomation mUiAutomation; + + public JankMonitorFactory(UiAutomation automation) { + mUiAutomation = automation; + } + + public List<JankMonitor> getJankMonitors(Method testMethod) { + List<JankMonitor> monitors = new ArrayList<JankMonitor>(); + if (testMethod.getAnnotation(GfxMonitor.class) != null) { + String process = testMethod.getAnnotation(GfxMonitor.class).processName(); + monitors.add(new GfxMonitorImpl(mUiAutomation, process)); + } + if (testMethod.getAnnotation(WindowContentFrameStatsMonitor.class) != null) { + monitors.add(new WindowContentFrameStatsMonitorImpl(mUiAutomation)); + } + if (testMethod.getAnnotation(WindowAnimationFrameStatsMonitor.class) != null) { + monitors.add(new WindowAnimationFrameStatsMonitorImpl(mUiAutomation)); + } + return monitors; + } +} diff --git a/jank/src/android/support/test/jank/internal/MetricsHelper.java b/jank/src/android/support/test/jank/internal/MetricsHelper.java new file mode 100644 index 0000000..8a88c88 --- /dev/null +++ b/jank/src/android/support/test/jank/internal/MetricsHelper.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2015 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.jank.internal; + +import android.os.Bundle; +import android.text.TextUtils; + +import java.util.List; + +class MetricsHelper { + + private static final String KEY_SEPARATOR = "-"; + private static final String MAX_VALUE_PREFIX = "max"; + private static final String AVG_VALUE_PREFIX = "avg"; + + private final String mMonitorPrefix; + + public MetricsHelper(String monitorPrefix) { + mMonitorPrefix = monitorPrefix; + } + + /** Stores the average metric for the given set of values. */ + public void putAverageMetric(Bundle metrics, String key, List<Double> values) { + double sum = 0.0f; + for (Double value : values) { + sum += value; + } + metrics.putDouble(joinKey(mMonitorPrefix, MAX_VALUE_PREFIX, key), sum / values.size()); + } + + /** Stores the average and max metrics for the given set of values. */ + public void putSummaryMetrics(Bundle metrics, String key, List<Integer> values) { + int max = -1; + int sum = 0; + for (Integer value : values) { + max = Math.max(max, value); + sum += value; + } + metrics.putInt(joinKey(mMonitorPrefix, MAX_VALUE_PREFIX, key), max); + metrics.putDouble(joinKey(mMonitorPrefix, AVG_VALUE_PREFIX, key), + (double)sum / values.size()); + } + + private String joinKey(String... parts) { + return TextUtils.join(KEY_SEPARATOR, parts); + } +} diff --git a/jank/src/android/support/test/jank/internal/WindowAnimationFrameMonitorImpl.java b/jank/src/android/support/test/jank/internal/WindowAnimationFrameMonitorImpl.java new file mode 100644 index 0000000..1fed858 --- /dev/null +++ b/jank/src/android/support/test/jank/internal/WindowAnimationFrameMonitorImpl.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2015 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.jank.internal; + +import android.app.UiAutomation; +import android.view.FrameStats; + +/** + * Monitors {@link android.view.WindowAnimationFrameStats} to detect janky frames. + * + * Reports average and max jank, as well as average frames per second and max frame times. + */ +class WindowAnimationFrameStatsMonitorImpl extends FrameStatsMonitorBase { + + private UiAutomation mUiAutomation; + + public WindowAnimationFrameStatsMonitorImpl(UiAutomation automation) { + mUiAutomation = automation; + } + + @Override + public void startIteration() { + // Clear out any previous data + mUiAutomation.clearWindowAnimationFrameStats(); + } + + @Override + public int stopIteration() { + FrameStats stats = mUiAutomation.getWindowAnimationFrameStats(); + analyze(stats); + return stats.getFrameCount(); + } +} diff --git a/jank/src/android/support/test/jank/internal/WindowContentFrameMonitorImpl.java b/jank/src/android/support/test/jank/internal/WindowContentFrameMonitorImpl.java new file mode 100644 index 0000000..9ff3a94 --- /dev/null +++ b/jank/src/android/support/test/jank/internal/WindowContentFrameMonitorImpl.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2015 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.jank.internal; + +import android.accessibilityservice.AccessibilityServiceInfo; +import android.app.UiAutomation; +import android.util.Log; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityWindowInfo; +import android.view.FrameStats; + +/** + * Monitors {@link android.view.WindowContentFrameStats} to detect janky frames. + * + * Reports average and max jank, as well as average frames per second and max frame times. + */ +class WindowContentFrameStatsMonitorImpl extends FrameStatsMonitorBase { + + private static final String TAG = "JankTestHelper"; + + private UiAutomation mUiAutomation; + private int mWindowId = -1; + + public WindowContentFrameStatsMonitorImpl(UiAutomation automation) { + mUiAutomation = automation; + } + + @Override + public void startIteration() { + // Save the window id + mWindowId = getCurrentWindow(); + + // Clear out any previous data + mUiAutomation.clearWindowContentFrameStats(mWindowId); + } + + @Override + public int stopIteration() { + int currentWindow = getCurrentWindow(); + if (currentWindow != mWindowId) { + Log.w(TAG, "Current window changed during the test. Did you mean to use " + + "WindowAnimationFrameStatsMonitor?"); + } + FrameStats stats = mUiAutomation.getWindowContentFrameStats(currentWindow); + analyze(stats); + + mWindowId = -1; + return stats.getFrameCount(); + } + + /** Returns the id of the current window. */ + private int getCurrentWindow() { + // Subscribe to window information + AccessibilityServiceInfo info = mUiAutomation.getServiceInfo(); + info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS; + mUiAutomation.setServiceInfo(info); + + AccessibilityNodeInfo activeWindowRoot = mUiAutomation.getRootInActiveWindow(); + + for (AccessibilityWindowInfo window : mUiAutomation.getWindows()) { + if (window.getRoot().equals(activeWindowRoot)) { + return window.getId(); + } + } + throw new RuntimeException("Could not find active window"); + } +} |