summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAllen Hair <allenhair@google.com>2015-03-06 18:43:33 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2015-03-06 18:43:33 +0000
commit33fe33ac4e56cadcc977a23b1d3ed5517986bbe3 (patch)
tree83f06c55968f45ee14f8f6124d081a1d637f2207
parent63a15ec1c8fa778107cd33d7ddddd1e3ec12294e (diff)
parentc0c4c33fce95ddc694f2231ffbc87b8de04485a5 (diff)
downloaduiautomator-33fe33ac4e56cadcc977a23b1d3ed5517986bbe3.tar.gz
Merge "Add support for dumpsys gfxinfo based jank detection."
-rw-r--r--jank/src/android/support/test/jank/GfxMonitor.java30
-rw-r--r--jank/src/android/support/test/jank/JankTest.java13
-rw-r--r--jank/src/android/support/test/jank/JankTestBase.java60
-rw-r--r--jank/src/android/support/test/jank/JankUtil.java129
-rw-r--r--jank/src/android/support/test/jank/WindowAnimationFrameStatsMonitor.java28
-rw-r--r--jank/src/android/support/test/jank/WindowContentFrameStatsMonitor.java (renamed from jank/src/android/support/test/jank/JankMetrics.java)22
-rw-r--r--jank/src/android/support/test/jank/internal/FrameStatsMonitorBase.java (renamed from jank/src/android/support/test/jank/JankResult.java)70
-rw-r--r--jank/src/android/support/test/jank/internal/GfxMonitorImpl.java194
-rw-r--r--jank/src/android/support/test/jank/internal/JankMonitor.java (renamed from jank/src/android/support/test/jank/JankType.java)15
-rw-r--r--jank/src/android/support/test/jank/internal/JankMonitorFactory.java50
-rw-r--r--jank/src/android/support/test/jank/internal/MetricsHelper.java61
-rw-r--r--jank/src/android/support/test/jank/internal/WindowAnimationFrameMonitorImpl.java47
-rw-r--r--jank/src/android/support/test/jank/internal/WindowContentFrameMonitorImpl.java81
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");
+ }
+}