1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.test.tilebenchmark;
18
19import com.test.tilebenchmark.ProfileActivity.ProfileCallback;
20
21import java.io.File;
22import java.util.HashMap;
23import java.util.Map;
24
25import android.content.res.Resources;
26import android.os.Bundle;
27import android.os.Environment;
28import android.test.ActivityInstrumentationTestCase2;
29import android.util.Log;
30import android.webkit.WebSettings;
31import android.widget.Spinner;
32
33public class PerformanceTest extends
34        ActivityInstrumentationTestCase2<ProfileActivity> {
35
36    public static class AnimStat {
37        double aggVal = 0;
38        double aggSqrVal = 0;
39        double count = 0;
40    }
41
42    private class StatAggregator extends PlaybackGraphs {
43        private HashMap<String, Double> mDataMap = new HashMap<String, Double>();
44        private HashMap<String, AnimStat> mAnimDataMap = new HashMap<String, AnimStat>();
45        private int mCount = 0;
46
47
48        public void aggregate() {
49            boolean inAnimTests = mAnimTests != null;
50            Resources resources = mWeb.getResources();
51            String animFramerateString = resources.getString(R.string.animation_framerate);
52            for (Map.Entry<String, Double> e : mSingleStats.entrySet()) {
53                String name = e.getKey();
54                if (inAnimTests) {
55                    if (name.equals(animFramerateString)) {
56                        // in animation testing phase, record animation framerate and aggregate
57                        // stats, differentiating on values of mAnimTestNr and mDoubleBuffering
58                        String fullName = ANIM_TEST_NAMES[mAnimTestNr] + " " + name;
59                        fullName += mDoubleBuffering ? " tiled" : " webkit";
60
61                        if (!mAnimDataMap.containsKey(fullName)) {
62                            mAnimDataMap.put(fullName, new AnimStat());
63                        }
64                        AnimStat statVals = mAnimDataMap.get(fullName);
65                        statVals.aggVal += e.getValue();
66                        statVals.aggSqrVal += e.getValue() * e.getValue();
67                        statVals.count += 1;
68                    }
69                } else {
70                    double aggVal = mDataMap.containsKey(name)
71                            ? mDataMap.get(name) : 0;
72                    mDataMap.put(name, aggVal + e.getValue());
73                }
74            }
75
76            if (inAnimTests) {
77                return;
78            }
79
80            mCount++;
81            for (int metricIndex = 0; metricIndex < Metrics.length; metricIndex++) {
82                for (int statIndex = 0; statIndex < Stats.length; statIndex++) {
83                    String metricLabel = resources.getString(
84                            Metrics[metricIndex].getLabelId());
85                    String statLabel = resources.getString(
86                            Stats[statIndex].getLabelId());
87
88                    String label = metricLabel + " " + statLabel;
89                    double aggVal = mDataMap.containsKey(label) ? mDataMap
90                            .get(label) : 0;
91
92                    aggVal += mStats[metricIndex][statIndex];
93                    mDataMap.put(label, aggVal);
94                }
95            }
96
97        }
98
99        // build the final bundle of results
100        public Bundle getBundle() {
101            Bundle b = new Bundle();
102            int count = (0 == mCount) ? Integer.MAX_VALUE : mCount;
103            for (Map.Entry<String, Double> e : mDataMap.entrySet()) {
104                b.putDouble(e.getKey(), e.getValue() / count);
105            }
106
107            for (Map.Entry<String, AnimStat> e : mAnimDataMap.entrySet()) {
108                String statName = e.getKey();
109                AnimStat statVals = e.getValue();
110
111                double avg = statVals.aggVal/statVals.count;
112                double stdDev = Math.sqrt((statVals.aggSqrVal / statVals.count) - avg * avg);
113
114                b.putDouble(statName, avg);
115                b.putDouble(statName + " STD DEV", stdDev);
116            }
117
118            return b;
119        }
120    }
121
122    ProfileActivity mActivity;
123    ProfiledWebView mWeb;
124    Spinner mMovementSpinner;
125    StatAggregator mStats;
126
127    private static final String LOGTAG = "PerformanceTest";
128    private static final String TEST_LOCATION = "webkit/page_cycler";
129    private static final String URL_PREFIX = "file://";
130    private static final String URL_POSTFIX = "/index.html?skip=true";
131    private static final int MAX_ITERATIONS = 4;
132    private static final String SCROLL_TEST_DIRS[] = {
133        "alexa25_2011"
134    };
135    private static final String ANIM_TEST_DIRS[] = {
136        "dhtml"
137    };
138
139    public PerformanceTest() {
140        super(ProfileActivity.class);
141    }
142
143    @Override
144    protected void setUp() throws Exception {
145        super.setUp();
146        mActivity = getActivity();
147        mWeb = (ProfiledWebView) mActivity.findViewById(R.id.web);
148        mMovementSpinner = (Spinner) mActivity.findViewById(R.id.movement);
149        mStats = new StatAggregator();
150
151        // use mStats as a condition variable between the UI thread and
152        // this(the testing) thread
153        mActivity.setCallback(new ProfileCallback() {
154            @Override
155            public void profileCallback(RunData data) {
156                mStats.setData(data);
157                synchronized (mStats) {
158                    mStats.notify();
159                }
160            }
161        });
162
163    }
164
165    private boolean loadUrl(final String url) {
166        try {
167            Log.d(LOGTAG, "test starting for url " + url);
168            mActivity.runOnUiThread(new Runnable() {
169                @Override
170                public void run() {
171                    mWeb.loadUrl(url);
172                }
173            });
174            synchronized (mStats) {
175                mStats.wait();
176            }
177
178            mStats.aggregate();
179        } catch (InterruptedException e) {
180            e.printStackTrace();
181            return false;
182        }
183        return true;
184    }
185
186    private boolean validTest(String nextTest) {
187        // if testing animations, test must be in mAnimTests
188        if (mAnimTests == null)
189            return true;
190
191        for (String test : mAnimTests) {
192            if (test.equals(nextTest)) {
193                return true;
194            }
195        }
196        return false;
197    }
198
199    private boolean runIteration(String[] testDirs) {
200        File sdFile = Environment.getExternalStorageDirectory();
201        for (String testDirName : testDirs) {
202            File testDir = new File(sdFile, TEST_LOCATION + "/" + testDirName);
203            Log.d(LOGTAG, "Testing dir: '" + testDir.getAbsolutePath()
204                    + "', exists=" + testDir.exists());
205
206            for (File siteDir : testDir.listFiles()) {
207                if (!siteDir.isDirectory() || !validTest(siteDir.getName())) {
208                    continue;
209                }
210
211                if (!loadUrl(URL_PREFIX + siteDir.getAbsolutePath()
212                        + URL_POSTFIX)) {
213                    return false;
214                }
215            }
216        }
217        return true;
218    }
219
220    private boolean  runTestDirs(String[] testDirs) {
221        for (int i = 0; i < MAX_ITERATIONS; i++)
222            if (!runIteration(testDirs)) {
223                return false;
224            }
225        return true;
226    }
227
228    private void pushDoubleBuffering() {
229        getInstrumentation().runOnMainSync(new Runnable() {
230            public void run() {
231                mWeb.setDoubleBuffering(mDoubleBuffering);
232            }
233        });
234    }
235
236    private void setScrollingTestingMode(final boolean scrolled) {
237        getInstrumentation().runOnMainSync(new Runnable() {
238            public void run() {
239                mMovementSpinner.setSelection(scrolled ? 0 : 2);
240            }
241        });
242    }
243
244
245    private String[] mAnimTests = null;
246    private int mAnimTestNr = -1;
247    private boolean mDoubleBuffering = true;
248    private static final String[] ANIM_TEST_NAMES = {
249        "slow", "fast"
250    };
251    private static final String[][] ANIM_TESTS = {
252        {"scrolling", "replaceimages", "layers5", "layers1"},
253        {"slidingballs", "meter", "slidein", "fadespacing", "colorfade",
254                "mozilla", "movingtext", "diagball", "zoom", "imageslide"},
255    };
256
257    private boolean checkMedia() {
258        String state = Environment.getExternalStorageState();
259
260        if (!Environment.MEDIA_MOUNTED.equals(state)
261                && !Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
262            Log.d(LOGTAG, "ARG Can't access sd card!");
263            // Can't read the SD card, fail and die!
264            getInstrumentation().sendStatus(1, null);
265            return false;
266        }
267        return true;
268    }
269
270    public void testMetrics() {
271        setScrollingTestingMode(true);
272        if (checkMedia() && runTestDirs(SCROLL_TEST_DIRS)) {
273            getInstrumentation().sendStatus(0, mStats.getBundle());
274        } else {
275            getInstrumentation().sendStatus(1, null);
276        }
277    }
278
279    public void testMetricsMinimalMemory() {
280        mActivity.runOnUiThread(new Runnable() {
281            @Override
282            public void run() {
283                mWeb.setUseMinimalMemory(true);
284            }
285        });
286
287        setScrollingTestingMode(true);
288        if (checkMedia() && runTestDirs(SCROLL_TEST_DIRS)) {
289            getInstrumentation().sendStatus(0, mStats.getBundle());
290        } else {
291            getInstrumentation().sendStatus(1, null);
292        }
293    }
294
295    private boolean runAnimationTests() {
296        for (int doubleBuffer = 0; doubleBuffer <= 1; doubleBuffer++) {
297            mDoubleBuffering = doubleBuffer == 1;
298            pushDoubleBuffering();
299            for (mAnimTestNr = 0; mAnimTestNr < ANIM_TESTS.length; mAnimTestNr++) {
300                mAnimTests = ANIM_TESTS[mAnimTestNr];
301                if (!runTestDirs(ANIM_TEST_DIRS)) {
302                    return false;
303                }
304            }
305        }
306        return true;
307    }
308
309    public void testAnimations() {
310        // instead of autoscrolling, load each page until either an timer fires,
311        // or the animation signals complete via javascript
312        setScrollingTestingMode(false);
313
314        if (checkMedia() && runAnimationTests()) {
315            getInstrumentation().sendStatus(0, mStats.getBundle());
316        } else {
317            getInstrumentation().sendStatus(1, null);
318        }
319    }
320}
321