Allow profiling of animation performance
Also fixes manual testing mode Depends on external/webkit change: https://android-git.corp.google.com/g/#/c/159794/ Change-Id: I169e80f9b5328b1b5a7b0aeaf09652de67febd8d
This commit is contained in:
@@ -49,8 +49,9 @@
|
||||
<!-- Drop down menu entry - automatically scroll to the end of the page
|
||||
with scrollBy() [CHAR LIMIT=15] -->
|
||||
<string name="movement_auto_scroll">Auto-scroll</string>
|
||||
<!-- Drop down menu entry - [CHAR LIMIT=15] -->
|
||||
<string name="movement_auto_fling">Auto-fling</string>
|
||||
<!-- Drop down menu entry - automatically record for a set time before
|
||||
stopping [CHAR LIMIT=15] -->
|
||||
<string name="movement_timed">Timed</string>
|
||||
<!-- Drop down menu entry - manually navigate the page(s), hit 'capture'
|
||||
button [CHAR LIMIT=15] -->
|
||||
<string name="movement_manual">Manual</string>
|
||||
@@ -67,14 +68,21 @@
|
||||
<!-- 75th percentile - 75% of frames fall below this value [CHAR LIMIT=12]
|
||||
-->
|
||||
<string name="percentile_75">75%ile</string>
|
||||
<!-- standard deviation [CHAR LIMIT=12] -->
|
||||
<string name="std_dev">StdDev</string>
|
||||
<!-- mean [CHAR LIMIT=12] -->
|
||||
<string name="mean">mean</string>
|
||||
|
||||
|
||||
|
||||
<!-- Frame rate [CHAR LIMIT=15] -->
|
||||
<string name="frames_per_second">Frames/sec</string>
|
||||
<!-- Portion of viewport covered by good tiles [CHAR LIMIT=15] -->
|
||||
<string name="viewport_coverage">Coverage</string>
|
||||
<!-- Milliseconds taken to inval, and re-render the page [CHAR LIMIT=15] -->
|
||||
<string name="render_millis">RenderMillis</string>
|
||||
<!-- Number of rendering stalls while running the test [CHAR LIMIT=15] -->
|
||||
<string name="render_stalls">Stalls</string>
|
||||
<!-- Animation Framerate [CHAR LIMIT=15] -->
|
||||
<string name="animation_framerate">AnimFramerate</string>
|
||||
<!-- Format string for stat value overlay [CHAR LIMIT=15] -->
|
||||
<string name="format_stat">%4.4f</string>
|
||||
|
||||
|
||||
@@ -27,17 +27,57 @@ import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.test.ActivityInstrumentationTestCase2;
|
||||
import android.util.Log;
|
||||
import android.webkit.WebSettings;
|
||||
import android.widget.Spinner;
|
||||
|
||||
public class PerformanceTest extends
|
||||
ActivityInstrumentationTestCase2<ProfileActivity> {
|
||||
|
||||
public static class AnimStat {
|
||||
double aggVal = 0;
|
||||
double aggSqrVal = 0;
|
||||
double count = 0;
|
||||
}
|
||||
|
||||
private class StatAggregator extends PlaybackGraphs {
|
||||
private HashMap<String, Double> mDataMap = new HashMap<String, Double>();
|
||||
private HashMap<String, AnimStat> mAnimDataMap = new HashMap<String, AnimStat>();
|
||||
private int mCount = 0;
|
||||
|
||||
|
||||
public void aggregate() {
|
||||
boolean inAnimTests = mAnimTests != null;
|
||||
Resources resources = mWeb.getResources();
|
||||
String animFramerateString = resources.getString(R.string.animation_framerate);
|
||||
for (Map.Entry<String, Double> e : mSingleStats.entrySet()) {
|
||||
String name = e.getKey();
|
||||
if (inAnimTests) {
|
||||
if (name.equals(animFramerateString)) {
|
||||
// in animation testing phase, record animation framerate and aggregate
|
||||
// stats, differentiating on values of mAnimTestNr and mDoubleBuffering
|
||||
String fullName = ANIM_TEST_NAMES[mAnimTestNr] + " " + name;
|
||||
fullName += mDoubleBuffering ? " tiled" : " webkit";
|
||||
|
||||
if (!mAnimDataMap.containsKey(fullName)) {
|
||||
mAnimDataMap.put(fullName, new AnimStat());
|
||||
}
|
||||
AnimStat statVals = mAnimDataMap.get(fullName);
|
||||
statVals.aggVal += e.getValue();
|
||||
statVals.aggSqrVal += e.getValue() * e.getValue();
|
||||
statVals.count += 1;
|
||||
}
|
||||
} else {
|
||||
double aggVal = mDataMap.containsKey(name)
|
||||
? mDataMap.get(name) : 0;
|
||||
mDataMap.put(name, aggVal + e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
if (inAnimTests) {
|
||||
return;
|
||||
}
|
||||
|
||||
mCount++;
|
||||
Resources resources = mView.getResources();
|
||||
for (int metricIndex = 0; metricIndex < Metrics.length; metricIndex++) {
|
||||
for (int statIndex = 0; statIndex < Stats.length; statIndex++) {
|
||||
String metricLabel = resources.getString(
|
||||
@@ -53,34 +93,47 @@ public class PerformanceTest extends
|
||||
mDataMap.put(label, aggVal);
|
||||
}
|
||||
}
|
||||
for (Map.Entry<String, Double> e : mSingleStats.entrySet()) {
|
||||
double aggVal = mDataMap.containsKey(e.getKey())
|
||||
? mDataMap.get(e.getKey()) : 0;
|
||||
mDataMap.put(e.getKey(), aggVal + e.getValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// build the final bundle of results
|
||||
public Bundle getBundle() {
|
||||
Bundle b = new Bundle();
|
||||
int count = 0 == mCount ? Integer.MAX_VALUE : mCount;
|
||||
int count = (0 == mCount) ? Integer.MAX_VALUE : mCount;
|
||||
for (Map.Entry<String, Double> e : mDataMap.entrySet()) {
|
||||
b.putDouble(e.getKey(), e.getValue() / count);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, AnimStat> e : mAnimDataMap.entrySet()) {
|
||||
String statName = e.getKey();
|
||||
AnimStat statVals = e.getValue();
|
||||
|
||||
double avg = statVals.aggVal/statVals.count;
|
||||
double stdDev = Math.sqrt((statVals.aggSqrVal / statVals.count) - avg * avg);
|
||||
|
||||
b.putDouble(statName, avg);
|
||||
b.putDouble(statName + " STD DEV", stdDev);
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
ProfileActivity mActivity;
|
||||
ProfiledWebView mView;
|
||||
StatAggregator mStats = new StatAggregator();
|
||||
ProfiledWebView mWeb;
|
||||
Spinner mMovementSpinner;
|
||||
StatAggregator mStats;
|
||||
|
||||
private static final String LOGTAG = "PerformanceTest";
|
||||
private static final String TEST_LOCATION = "webkit/page_cycler";
|
||||
private static final String URL_PREFIX = "file://";
|
||||
private static final String URL_POSTFIX = "/index.html?skip=true";
|
||||
private static final int MAX_ITERATIONS = 4;
|
||||
private static final String TEST_DIRS[] = {
|
||||
"alexa25_2011"//, "alexa_us", "android", "dom", "intl2", "moz", "moz2"
|
||||
private static final String SCROLL_TEST_DIRS[] = {
|
||||
"alexa25_2011"
|
||||
};
|
||||
private static final String ANIM_TEST_DIRS[] = {
|
||||
"dhtml"
|
||||
};
|
||||
|
||||
public PerformanceTest() {
|
||||
@@ -91,7 +144,22 @@ public class PerformanceTest extends
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
mActivity = getActivity();
|
||||
mView = (ProfiledWebView) mActivity.findViewById(R.id.web);
|
||||
mWeb = (ProfiledWebView) mActivity.findViewById(R.id.web);
|
||||
mMovementSpinner = (Spinner) mActivity.findViewById(R.id.movement);
|
||||
mStats = new StatAggregator();
|
||||
|
||||
// use mStats as a condition variable between the UI thread and
|
||||
// this(the testing) thread
|
||||
mActivity.setCallback(new ProfileCallback() {
|
||||
@Override
|
||||
public void profileCallback(RunData data) {
|
||||
mStats.setData(data);
|
||||
synchronized (mStats) {
|
||||
mStats.notify();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private boolean loadUrl(final String url) {
|
||||
@@ -100,12 +168,13 @@ public class PerformanceTest extends
|
||||
mActivity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mView.loadUrl(url);
|
||||
mWeb.loadUrl(url);
|
||||
}
|
||||
});
|
||||
synchronized (mStats) {
|
||||
mStats.wait();
|
||||
}
|
||||
|
||||
mStats.aggregate();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
@@ -114,15 +183,30 @@ public class PerformanceTest extends
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean runIteration() {
|
||||
private boolean validTest(String nextTest) {
|
||||
// if testing animations, test must be in mAnimTests
|
||||
if (mAnimTests == null)
|
||||
return true;
|
||||
|
||||
for (String test : mAnimTests) {
|
||||
if (test.equals(nextTest)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean runIteration(String[] testDirs) {
|
||||
File sdFile = Environment.getExternalStorageDirectory();
|
||||
for (String testDirName : TEST_DIRS) {
|
||||
for (String testDirName : testDirs) {
|
||||
File testDir = new File(sdFile, TEST_LOCATION + "/" + testDirName);
|
||||
Log.d(LOGTAG, "Testing dir: '" + testDir.getAbsolutePath()
|
||||
+ "', exists=" + testDir.exists());
|
||||
|
||||
for (File siteDir : testDir.listFiles()) {
|
||||
if (!siteDir.isDirectory())
|
||||
if (!siteDir.isDirectory() || !validTest(siteDir.getName())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!loadUrl(URL_PREFIX + siteDir.getAbsolutePath()
|
||||
+ URL_POSTFIX)) {
|
||||
@@ -133,7 +217,44 @@ public class PerformanceTest extends
|
||||
return true;
|
||||
}
|
||||
|
||||
public void testMetrics() {
|
||||
private boolean runTestDirs(String[] testDirs) {
|
||||
for (int i = 0; i < MAX_ITERATIONS; i++)
|
||||
if (!runIteration(testDirs)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void pushDoubleBuffering() {
|
||||
getInstrumentation().runOnMainSync(new Runnable() {
|
||||
public void run() {
|
||||
mWeb.setDoubleBuffering(mDoubleBuffering);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setScrollingTestingMode(final boolean scrolled) {
|
||||
getInstrumentation().runOnMainSync(new Runnable() {
|
||||
public void run() {
|
||||
mMovementSpinner.setSelection(scrolled ? 0 : 2);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private String[] mAnimTests = null;
|
||||
private int mAnimTestNr = -1;
|
||||
private boolean mDoubleBuffering = true;
|
||||
private static final String[] ANIM_TEST_NAMES = {
|
||||
"slow", "fast"
|
||||
};
|
||||
private static final String[][] ANIM_TESTS = {
|
||||
{"scrolling", "replaceimages", "layers5", "layers1"},
|
||||
{"slidingballs", "meter", "slidein", "fadespacing", "colorfade",
|
||||
"mozilla", "movingtext", "diagball", "zoom", "imageslide"},
|
||||
};
|
||||
|
||||
private boolean checkMedia() {
|
||||
String state = Environment.getExternalStorageState();
|
||||
|
||||
if (!Environment.MEDIA_MOUNTED.equals(state)
|
||||
@@ -141,27 +262,43 @@ public class PerformanceTest extends
|
||||
Log.d(LOGTAG, "ARG Can't access sd card!");
|
||||
// Can't read the SD card, fail and die!
|
||||
getInstrumentation().sendStatus(1, null);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// use mGraphs as a condition variable between the UI thread and
|
||||
// this(the testing) thread
|
||||
mActivity.setCallback(new ProfileCallback() {
|
||||
@Override
|
||||
public void profileCallback(RunData data) {
|
||||
Log.d(LOGTAG, "test completion callback");
|
||||
mStats.setData(data);
|
||||
synchronized (mStats) {
|
||||
mStats.notify();
|
||||
public void testMetrics() {
|
||||
setScrollingTestingMode(true);
|
||||
if (checkMedia() && runTestDirs(SCROLL_TEST_DIRS)) {
|
||||
getInstrumentation().sendStatus(0, mStats.getBundle());
|
||||
} else {
|
||||
getInstrumentation().sendStatus(1, null);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean runAnimationTests() {
|
||||
for (int doubleBuffer = 0; doubleBuffer <= 1; doubleBuffer++) {
|
||||
mDoubleBuffering = doubleBuffer == 1;
|
||||
pushDoubleBuffering();
|
||||
for (mAnimTestNr = 0; mAnimTestNr < ANIM_TESTS.length; mAnimTestNr++) {
|
||||
mAnimTests = ANIM_TESTS[mAnimTestNr];
|
||||
if (!runTestDirs(ANIM_TEST_DIRS)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_ITERATIONS; i++)
|
||||
if (!runIteration()) {
|
||||
getInstrumentation().sendStatus(1, null);
|
||||
return;
|
||||
}
|
||||
getInstrumentation().sendStatus(0, mStats.getBundle());
|
||||
public void testAnimations() {
|
||||
// instead of autoscrolling, load each page until either an timer fires,
|
||||
// or the animation signals complete via javascript
|
||||
setScrollingTestingMode(false);
|
||||
|
||||
if (checkMedia() && runAnimationTests()) {
|
||||
getInstrumentation().sendStatus(0, mStats.getBundle());
|
||||
} else {
|
||||
getInstrumentation().sendStatus(1, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ public class PlaybackGraphs {
|
||||
for (int tileID = 1; tileID < frame.length; tileID++) {
|
||||
TileData data = frame[tileID];
|
||||
double coverage = viewportCoverage(frame[0], data);
|
||||
total += coverage * (data.isReady ? 1 : 0);
|
||||
total += coverage * (data.isReady ? 100 : 0);
|
||||
totalCount += coverage;
|
||||
}
|
||||
if (totalCount == 0) {
|
||||
@@ -91,7 +91,7 @@ public class PlaybackGraphs {
|
||||
|
||||
@Override
|
||||
public double getMax() {
|
||||
return 1;
|
||||
return 100;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -108,6 +108,9 @@ public class PlaybackGraphs {
|
||||
}
|
||||
|
||||
public static double getPercentile(double sortedValues[], double ratioAbove) {
|
||||
if (sortedValues.length == 0)
|
||||
return -1;
|
||||
|
||||
double index = ratioAbove * (sortedValues.length - 1);
|
||||
int intIndex = (int) Math.floor(index);
|
||||
if (index == intIndex) {
|
||||
@@ -118,6 +121,31 @@ public class PlaybackGraphs {
|
||||
+ sortedValues[intIndex + 1] * (alpha);
|
||||
}
|
||||
|
||||
public static double getMean(double sortedValues[]) {
|
||||
if (sortedValues.length == 0)
|
||||
return -1;
|
||||
|
||||
double agg = 0;
|
||||
for (double val : sortedValues) {
|
||||
agg += val;
|
||||
}
|
||||
return agg / sortedValues.length;
|
||||
}
|
||||
|
||||
public static double getStdDev(double sortedValues[]) {
|
||||
if (sortedValues.length == 0)
|
||||
return -1;
|
||||
|
||||
double agg = 0;
|
||||
double sqrAgg = 0;
|
||||
for (double val : sortedValues) {
|
||||
agg += val;
|
||||
sqrAgg += val*val;
|
||||
}
|
||||
double mean = agg / sortedValues.length;
|
||||
return Math.sqrt((sqrAgg / sortedValues.length) - (mean * mean));
|
||||
}
|
||||
|
||||
protected static StatGen[] Stats = new StatGen[] {
|
||||
new StatGen() {
|
||||
@Override
|
||||
@@ -149,6 +177,26 @@ public class PlaybackGraphs {
|
||||
public int getLabelId() {
|
||||
return R.string.percentile_75;
|
||||
}
|
||||
}, new StatGen() {
|
||||
@Override
|
||||
public double getValue(double[] sortedValues) {
|
||||
return getStdDev(sortedValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLabelId() {
|
||||
return R.string.std_dev;
|
||||
}
|
||||
}, new StatGen() {
|
||||
@Override
|
||||
public double getValue(double[] sortedValues) {
|
||||
return getMean(sortedValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLabelId() {
|
||||
return R.string.mean;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -159,40 +207,47 @@ public class PlaybackGraphs {
|
||||
}
|
||||
|
||||
private ArrayList<ShapeDrawable> mShapes = new ArrayList<ShapeDrawable>();
|
||||
protected double[][] mStats = new double[Metrics.length][Stats.length];
|
||||
protected final double[][] mStats = new double[Metrics.length][Stats.length];
|
||||
protected HashMap<String, Double> mSingleStats;
|
||||
|
||||
private void gatherFrameMetric(int metricIndex, double metricValues[], RunData data) {
|
||||
// create graph out of rectangles, one per frame
|
||||
int lastBar = 0;
|
||||
for (int frameIndex = 0; frameIndex < data.frames.length; frameIndex++) {
|
||||
TileData frame[] = data.frames[frameIndex];
|
||||
int newBar = (frame[0].top + frame[0].bottom) / 2;
|
||||
|
||||
MetricGen s = Metrics[metricIndex];
|
||||
double absoluteValue = s.getValue(frame);
|
||||
double relativeValue = absoluteValue / s.getMax();
|
||||
relativeValue = Math.min(1,relativeValue);
|
||||
relativeValue = Math.max(0,relativeValue);
|
||||
int rightPos = (int) (-BAR_WIDTH * metricIndex);
|
||||
int leftPos = (int) (-BAR_WIDTH * (metricIndex + relativeValue));
|
||||
|
||||
ShapeDrawable graphBar = new ShapeDrawable();
|
||||
graphBar.getPaint().setColor(Color.BLUE);
|
||||
graphBar.setBounds(leftPos, lastBar, rightPos, newBar);
|
||||
|
||||
mShapes.add(graphBar);
|
||||
metricValues[frameIndex] = absoluteValue;
|
||||
lastBar = newBar;
|
||||
}
|
||||
}
|
||||
|
||||
public void setData(RunData data) {
|
||||
mShapes.clear();
|
||||
double metricValues[] = new double[data.frames.length];
|
||||
|
||||
mSingleStats = data.singleStats;
|
||||
|
||||
if (data.frames.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int metricIndex = 0; metricIndex < Metrics.length; metricIndex++) {
|
||||
// create graph out of rectangles, one per frame
|
||||
int lastBar = 0;
|
||||
for (int frameIndex = 0; frameIndex < data.frames.length; frameIndex++) {
|
||||
TileData frame[] = data.frames[frameIndex];
|
||||
int newBar = (frame[0].top + frame[0].bottom) / 2;
|
||||
|
||||
MetricGen s = Metrics[metricIndex];
|
||||
double absoluteValue = s.getValue(frame);
|
||||
double relativeValue = absoluteValue / s.getMax();
|
||||
relativeValue = Math.min(1,relativeValue);
|
||||
relativeValue = Math.max(0,relativeValue);
|
||||
int rightPos = (int) (-BAR_WIDTH * metricIndex);
|
||||
int leftPos = (int) (-BAR_WIDTH * (metricIndex + relativeValue));
|
||||
|
||||
ShapeDrawable graphBar = new ShapeDrawable();
|
||||
graphBar.getPaint().setColor(Color.BLUE);
|
||||
graphBar.setBounds(leftPos, lastBar, rightPos, newBar);
|
||||
|
||||
mShapes.add(graphBar);
|
||||
metricValues[frameIndex] = absoluteValue;
|
||||
lastBar = newBar;
|
||||
}
|
||||
// calculate metric based on list of frames
|
||||
gatherFrameMetric(metricIndex, metricValues, data);
|
||||
|
||||
// store aggregate statistics per metric (median, and similar)
|
||||
Arrays.sort(metricValues);
|
||||
@@ -200,8 +255,6 @@ public class PlaybackGraphs {
|
||||
mStats[metricIndex][statIndex] =
|
||||
Stats[statIndex].getValue(metricValues);
|
||||
}
|
||||
|
||||
mSingleStats = data.singleStats;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,11 +22,12 @@ import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.CountDownTimer;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.AdapterView;
|
||||
@@ -49,6 +50,8 @@ import java.io.ObjectOutputStream;
|
||||
*/
|
||||
public class ProfileActivity extends Activity {
|
||||
|
||||
private static final int TIMED_RECORD_MILLIS = 2000;
|
||||
|
||||
public interface ProfileCallback {
|
||||
public void profileCallback(RunData data);
|
||||
}
|
||||
@@ -65,6 +68,7 @@ public class ProfileActivity extends Activity {
|
||||
|
||||
LoggingWebViewClient mLoggingWebViewClient = new LoggingWebViewClient();
|
||||
AutoLoggingWebViewClient mAutoLoggingWebViewClient = new AutoLoggingWebViewClient();
|
||||
TimedLoggingWebViewClient mTimedLoggingWebViewClient = new TimedLoggingWebViewClient();
|
||||
|
||||
private enum TestingState {
|
||||
NOT_TESTING,
|
||||
@@ -93,18 +97,18 @@ public class ProfileActivity extends Activity {
|
||||
public void onItemSelected(AdapterView<?> parent, View view,
|
||||
int position, long id) {
|
||||
String movementStr = parent.getItemAtPosition(position).toString();
|
||||
if (movementStr == getResources().getString(
|
||||
R.string.movement_auto_scroll)
|
||||
|| movementStr == getResources().getString(
|
||||
R.string.movement_auto_fling)) {
|
||||
if (movementStr == getResources().getString(R.string.movement_auto_scroll)) {
|
||||
mWeb.setWebViewClient(mAutoLoggingWebViewClient);
|
||||
mCaptureButton.setEnabled(false);
|
||||
mVelocitySpinner.setEnabled(true);
|
||||
} else if (movementStr == getResources().getString(
|
||||
R.string.movement_manual)) {
|
||||
} else if (movementStr == getResources().getString(R.string.movement_manual)) {
|
||||
mWeb.setWebViewClient(mLoggingWebViewClient);
|
||||
mCaptureButton.setEnabled(true);
|
||||
mVelocitySpinner.setEnabled(false);
|
||||
} else if (movementStr == getResources().getString(R.string.movement_timed)) {
|
||||
mWeb.setWebViewClient(mTimedLoggingWebViewClient);
|
||||
mCaptureButton.setEnabled(false);
|
||||
mVelocitySpinner.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,15 +128,19 @@ public class ProfileActivity extends Activity {
|
||||
super.onPageStarted(view, url, favicon);
|
||||
mUrl.setText(url);
|
||||
}
|
||||
}
|
||||
|
||||
private class AutoLoggingWebViewClient extends LoggingWebViewClient {
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
super.onPageFinished(view, url);
|
||||
view.requestFocus();
|
||||
((ProfiledWebView)view).onPageFinished();
|
||||
}
|
||||
}
|
||||
|
||||
private class AutoLoggingWebViewClient extends LoggingWebViewClient {
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
super.onPageFinished(view, url);
|
||||
startViewProfiling(true);
|
||||
}
|
||||
|
||||
@@ -143,6 +151,32 @@ public class ProfileActivity extends Activity {
|
||||
}
|
||||
}
|
||||
|
||||
private class TimedLoggingWebViewClient extends LoggingWebViewClient {
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
super.onPageFinished(view, url);
|
||||
startViewProfiling(false);
|
||||
|
||||
// after a fixed time after page finished, stop testing
|
||||
new CountDownTimer(TIMED_RECORD_MILLIS, TIMED_RECORD_MILLIS) {
|
||||
@Override
|
||||
public void onTick(long millisUntilFinished) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish() {
|
||||
mWeb.stopScrollTest();
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||
super.onPageStarted(view, url, favicon);
|
||||
setTestingState(TestingState.PRE_TESTING);
|
||||
}
|
||||
}
|
||||
|
||||
private class StoreFileTask extends
|
||||
AsyncTask<Pair<String, RunData>, Void, Void> {
|
||||
|
||||
@@ -178,11 +212,13 @@ public class ProfileActivity extends Activity {
|
||||
mMovementSpinner.setEnabled(false);
|
||||
break;
|
||||
case START_TESTING:
|
||||
mCaptureButton.setChecked(true);
|
||||
mUrl.setBackgroundResource(R.color.background_start_testing);
|
||||
mInspectButton.setEnabled(false);
|
||||
mMovementSpinner.setEnabled(false);
|
||||
break;
|
||||
case STOP_TESTING:
|
||||
mCaptureButton.setChecked(false);
|
||||
mUrl.setBackgroundResource(R.color.background_stop_testing);
|
||||
break;
|
||||
case SAVED_TESTING:
|
||||
@@ -195,7 +231,6 @@ public class ProfileActivity extends Activity {
|
||||
/** auto - automatically scroll. */
|
||||
private void startViewProfiling(boolean auto) {
|
||||
// toggle capture button to indicate capture state to user
|
||||
mCaptureButton.setChecked(true);
|
||||
mWeb.startScrollTest(mCallback, auto);
|
||||
setTestingState(TestingState.START_TESTING);
|
||||
}
|
||||
@@ -217,7 +252,7 @@ public class ProfileActivity extends Activity {
|
||||
public void profileCallback(RunData data) {
|
||||
new StoreFileTask().execute(new Pair<String, RunData>(
|
||||
TEMP_FILENAME, data));
|
||||
mCaptureButton.setChecked(false);
|
||||
Log.d("ProfileActivity", "stored " + data.frames.length + " frames in file");
|
||||
setTestingState(TestingState.STOP_TESTING);
|
||||
}
|
||||
});
|
||||
@@ -245,8 +280,8 @@ public class ProfileActivity extends Activity {
|
||||
// Movement spinner
|
||||
String content[] = {
|
||||
getResources().getString(R.string.movement_auto_scroll),
|
||||
getResources().getString(R.string.movement_auto_fling),
|
||||
getResources().getString(R.string.movement_manual)
|
||||
getResources().getString(R.string.movement_manual),
|
||||
getResources().getString(R.string.movement_timed)
|
||||
};
|
||||
adapter = new ArrayAdapter<CharSequence>(this,
|
||||
android.R.layout.simple_spinner_item, content);
|
||||
@@ -270,13 +305,7 @@ public class ProfileActivity extends Activity {
|
||||
});
|
||||
|
||||
// Custom profiling WebView
|
||||
WebSettings settings = mWeb.getSettings();
|
||||
settings.setJavaScriptEnabled(true);
|
||||
settings.setSupportZoom(true);
|
||||
settings.setEnableSmoothTransition(true);
|
||||
settings.setBuiltInZoomControls(true);
|
||||
settings.setLoadWithOverviewMode(true);
|
||||
settings.setProperty("use_minimal_memory", "false"); // prefetch tiles, as browser does
|
||||
mWeb.init(this);
|
||||
mWeb.setWebViewClient(new LoggingWebViewClient());
|
||||
|
||||
// URL text entry
|
||||
|
||||
@@ -20,21 +20,28 @@ import android.content.Context;
|
||||
import android.os.CountDownTimer;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.test.tilebenchmark.ProfileActivity.ProfileCallback;
|
||||
import com.test.tilebenchmark.RunData.TileData;
|
||||
|
||||
public class ProfiledWebView extends WebView {
|
||||
private static final String LOGTAG = "ProfiledWebView";
|
||||
|
||||
private int mSpeed;
|
||||
|
||||
private boolean mIsTesting = false;
|
||||
private boolean mIsScrolling = false;
|
||||
private ProfileCallback mCallback;
|
||||
private long mContentInvalMillis;
|
||||
private boolean mHadToBeForced = false;
|
||||
private static final int LOAD_STALL_MILLIS = 2000; // nr of millis after load,
|
||||
// before test is forced
|
||||
private double mLoadTime;
|
||||
private double mAnimationTime;
|
||||
|
||||
public ProfiledWebView(Context context) {
|
||||
super(context);
|
||||
@@ -53,6 +60,39 @@ public class ProfiledWebView extends WebView {
|
||||
super(context, attrs, defStyle, privateBrowsing);
|
||||
}
|
||||
|
||||
private class JavaScriptInterface {
|
||||
Context mContext;
|
||||
|
||||
/** Instantiate the interface and set the context */
|
||||
JavaScriptInterface(Context c) {
|
||||
mContext = c;
|
||||
}
|
||||
|
||||
/** Show a toast from the web page */
|
||||
public void animationComplete() {
|
||||
Toast.makeText(mContext, "Animation complete!", Toast.LENGTH_SHORT).show();
|
||||
//Log.d(LOGTAG, "anim complete");
|
||||
mAnimationTime = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
public void init(Context c) {
|
||||
WebSettings settings = getSettings();
|
||||
settings.setJavaScriptEnabled(true);
|
||||
settings.setSupportZoom(true);
|
||||
settings.setEnableSmoothTransition(true);
|
||||
settings.setBuiltInZoomControls(true);
|
||||
settings.setLoadWithOverviewMode(true);
|
||||
settings.setProperty("use_minimal_memory", "false"); // prefetch tiles, as browser does
|
||||
addJavascriptInterface(new JavaScriptInterface(c), "Android");
|
||||
mAnimationTime = 0;
|
||||
mLoadTime = 0;
|
||||
}
|
||||
|
||||
public void onPageFinished() {
|
||||
mLoadTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(android.graphics.Canvas canvas) {
|
||||
if (mIsTesting && mIsScrolling) {
|
||||
@@ -72,9 +112,12 @@ public class ProfiledWebView extends WebView {
|
||||
* scrolling, invalidate all content and redraw it, measuring time taken.
|
||||
*/
|
||||
public void startScrollTest(ProfileCallback callback, boolean autoScrolling) {
|
||||
mIsScrolling = autoScrolling;
|
||||
mCallback = callback;
|
||||
mIsTesting = false;
|
||||
mIsScrolling = false;
|
||||
WebSettings settings = getSettings();
|
||||
settings.setProperty("tree_updates", "0");
|
||||
|
||||
|
||||
if (autoScrolling) {
|
||||
// after a while, force it to start even if the pages haven't swapped
|
||||
@@ -86,13 +129,18 @@ public class ProfiledWebView extends WebView {
|
||||
@Override
|
||||
public void onFinish() {
|
||||
// invalidate all content, and kick off redraw
|
||||
Log.d("ProfiledWebView",
|
||||
"kicking off test with callback registration, and tile discard...");
|
||||
registerPageSwapCallback();
|
||||
discardAllTextures();
|
||||
invalidate();
|
||||
|
||||
mIsScrolling = true;
|
||||
mContentInvalMillis = System.currentTimeMillis();
|
||||
}
|
||||
}.start();
|
||||
} else {
|
||||
mIsTesting = true;
|
||||
tileProfilingStart();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,13 +150,35 @@ public class ProfiledWebView extends WebView {
|
||||
*/
|
||||
@Override
|
||||
protected void pageSwapCallback(boolean startAnim) {
|
||||
mContentInvalMillis = System.currentTimeMillis() - mContentInvalMillis;
|
||||
super.pageSwapCallback(startAnim);
|
||||
Log.d("ProfiledWebView", "REDRAW TOOK " + mContentInvalMillis
|
||||
+ "millis");
|
||||
mIsTesting = true;
|
||||
invalidate(); // ensure a redraw so that auto-scrolling can occur
|
||||
tileProfilingStart();
|
||||
if (!mIsTesting && mIsScrolling) {
|
||||
// kick off testing
|
||||
mContentInvalMillis = System.currentTimeMillis() - mContentInvalMillis;
|
||||
super.pageSwapCallback(startAnim);
|
||||
Log.d("ProfiledWebView", "REDRAW TOOK " + mContentInvalMillis + "millis");
|
||||
mIsTesting = true;
|
||||
invalidate(); // ensure a redraw so that auto-scrolling can occur
|
||||
tileProfilingStart();
|
||||
}
|
||||
}
|
||||
|
||||
private double animFramerate() {
|
||||
WebSettings settings = getSettings();
|
||||
String updatesString = settings.getProperty("tree_updates");
|
||||
int updates = (updatesString == null) ? -1 : Integer.parseInt(updatesString);
|
||||
|
||||
double animationTime;
|
||||
if (mAnimationTime == 0) {
|
||||
animationTime = System.currentTimeMillis() - mLoadTime;
|
||||
} else {
|
||||
animationTime = mAnimationTime - mLoadTime;
|
||||
}
|
||||
|
||||
return updates * 1000 / animationTime;
|
||||
}
|
||||
|
||||
public void setDoubleBuffering(boolean useDoubleBuffering) {
|
||||
WebSettings settings = getSettings();
|
||||
settings.setProperty("use_double_buffering", useDoubleBuffering ? "true" : "false");
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -127,11 +197,12 @@ public class ProfiledWebView extends WebView {
|
||||
// record the time spent (before scrolling) rendering the page
|
||||
data.singleStats.put(getResources().getString(R.string.render_millis),
|
||||
(double)mContentInvalMillis);
|
||||
// record if the page render timed out
|
||||
Log.d("ProfiledWebView", "hadtobeforced = " + mHadToBeForced);
|
||||
data.singleStats.put(getResources().getString(R.string.render_stalls),
|
||||
mHadToBeForced ? 1.0 : 0.0);
|
||||
mHadToBeForced = false;
|
||||
|
||||
// record framerate
|
||||
double framerate = animFramerate();
|
||||
Log.d(LOGTAG, "anim framerate was "+framerate);
|
||||
data.singleStats.put(getResources().getString(R.string.animation_framerate),
|
||||
framerate);
|
||||
|
||||
for (int frame = 0; frame < data.frames.length; frame++) {
|
||||
data.frames[frame] = new TileData[
|
||||
@@ -159,6 +230,8 @@ public class ProfiledWebView extends WebView {
|
||||
|
||||
@Override
|
||||
public void loadUrl(String url) {
|
||||
mAnimationTime = 0;
|
||||
mLoadTime = 0;
|
||||
if (!url.startsWith("http://") && !url.startsWith("file://")) {
|
||||
url = "http://" + url;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user