1/*
2 * Copyright (C) 2008 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.android.dumprendertree;
18
19import com.android.dumprendertree.forwarder.AdbUtils;
20import com.android.dumprendertree.forwarder.ForwardServer;
21
22import android.app.Activity;
23import android.app.Instrumentation;
24import android.content.Context;
25import android.content.Intent;
26import android.os.Bundle;
27import android.os.Debug;
28import android.os.Environment;
29import android.os.Process;
30import android.test.ActivityInstrumentationTestCase2;
31import android.util.Log;
32
33import java.io.File;
34import java.io.FileOutputStream;
35import java.io.IOException;
36import java.io.InputStream;
37import java.io.OutputStream;
38import java.io.PrintStream;
39import java.util.concurrent.CountDownLatch;
40import java.util.concurrent.TimeUnit;
41import java.util.regex.Matcher;
42import java.util.regex.Pattern;
43
44public class LoadTestsAutoTest extends ActivityInstrumentationTestCase2<TestShellActivity> {
45
46    private final static String LOGTAG = "LoadTest";
47    private final static String LOAD_TEST_RESULT =
48        Environment.getExternalStorageDirectory() + "/load_test_result.txt";
49    private final static int MAX_GC_WAIT_SEC = 10;
50    private final static int LOCAL_PORT = 17171;
51    private boolean mFinished;
52    static final String LOAD_TEST_RUNNER_FILES[] = {
53        "run_page_cycler.py"
54    };
55    private ForwardServer mForwardServer;
56
57    public LoadTestsAutoTest() {
58        super(TestShellActivity.class);
59    }
60
61    // This function writes the result of the layout test to
62    // Am status so that it can be picked up from a script.
63    public void passOrFailCallback(String file, boolean result) {
64        Instrumentation inst = getInstrumentation();
65        Bundle bundle = new Bundle();
66        bundle.putBoolean(file, result);
67        inst.sendStatus(0, bundle);
68    }
69
70    private String setUpForwarding(String forwardInfo, String suite, String iteration) throws IOException {
71        // read forwarding information first
72        Pattern forwardPattern = Pattern.compile("(.*):(\\d+)/(.*)/");
73        Matcher matcher = forwardPattern.matcher(forwardInfo);
74        if (!matcher.matches()) {
75            throw new RuntimeException("Invalid forward information");
76        }
77        String host = matcher.group(1);
78        int port = Integer.parseInt(matcher.group(2));
79        mForwardServer = new ForwardServer(LOCAL_PORT, AdbUtils.resolve(host), port);
80        mForwardServer.start();
81        return String.format("http://127.0.0.1:%d/%s/%s/start.html?auto=1&iterations=%s",
82                LOCAL_PORT, matcher.group(3), suite, iteration);
83    }
84
85    // Invokes running of layout tests
86    // and waits till it has finished running.
87    public void runPageCyclerTest() throws IOException {
88        LayoutTestsAutoRunner runner = (LayoutTestsAutoRunner) getInstrumentation();
89
90        if (runner.mPageCyclerSuite != null) {
91            // start forwarder to use page cycler suites hosted on external web server
92            if (runner.mPageCyclerForwardHost == null) {
93                throw new RuntimeException("no forwarder information provided");
94            }
95            runner.mTestPath = setUpForwarding(runner.mPageCyclerForwardHost,
96                    runner.mPageCyclerSuite, runner.mPageCyclerIteration);
97            Log.d(LOGTAG, "using path: " + runner.mTestPath);
98        }
99
100        if (runner.mTestPath == null) {
101            throw new RuntimeException("No test specified");
102        }
103
104        final TestShellActivity activity = (TestShellActivity) getActivity();
105
106        Log.v(LOGTAG, "About to run tests, calling gc first...");
107        freeMem();
108
109        // Run tests
110        runTestAndWaitUntilDone(activity, runner.mTestPath, runner.mTimeoutInMillis);
111
112        getInstrumentation().runOnMainSync(new Runnable() {
113
114            @Override
115            public void run() {
116                activity.clearCache();
117            }
118        });
119        if (mForwardServer != null) {
120            mForwardServer.stop();
121            mForwardServer = null;
122        }
123        try {
124            Thread.sleep(5000);
125        } catch (InterruptedException e) {
126        }
127        dumpMemoryInfo();
128
129        // Kill activity
130        activity.finish();
131    }
132
133    private void freeMem() {
134        Log.v(LOGTAG, "freeMem: calling gc...");
135        final CountDownLatch latch = new CountDownLatch(1);
136        @SuppressWarnings("unused")
137        Object dummy = new Object() {
138            // this object instance is used to track gc
139            @Override
140            protected void finalize() throws Throwable {
141                latch.countDown();
142                super.finalize();
143            }
144        };
145        dummy = null;
146        System.gc();
147        try {
148            if (!latch.await(MAX_GC_WAIT_SEC, TimeUnit.SECONDS)) {
149                Log.w(LOGTAG, "gc did not happen in 10s");
150            }
151        } catch (InterruptedException e) {
152            //ignore
153        }
154    }
155
156    private void printRow(PrintStream ps, String format, Object...objs) {
157        ps.println(String.format(format, objs));
158    }
159
160    private void dumpMemoryInfo() {
161        try {
162            freeMem();
163            Log.v(LOGTAG, "Dumping memory information.");
164
165            FileOutputStream out = new FileOutputStream(LOAD_TEST_RESULT, true);
166            PrintStream ps = new PrintStream(out);
167
168            ps.print("\n\n\n");
169            ps.println("** MEMINFO in pid " + Process.myPid()
170                    + " [com.android.dumprendertree] **");
171            String formatString = "%17s %8s %8s %8s %8s";
172
173            long nativeMax = Debug.getNativeHeapSize() / 1024;
174            long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024;
175            long nativeFree = Debug.getNativeHeapFreeSize() / 1024;
176            Runtime runtime = Runtime.getRuntime();
177            long dalvikMax = runtime.totalMemory() / 1024;
178            long dalvikFree = runtime.freeMemory() / 1024;
179            long dalvikAllocated = dalvikMax - dalvikFree;
180
181
182            Debug.MemoryInfo memInfo = new Debug.MemoryInfo();
183            Debug.getMemoryInfo(memInfo);
184
185            final int nativeShared = memInfo.nativeSharedDirty;
186            final int dalvikShared = memInfo.dalvikSharedDirty;
187            final int otherShared = memInfo.otherSharedDirty;
188
189            final int nativePrivate = memInfo.nativePrivateDirty;
190            final int dalvikPrivate = memInfo.dalvikPrivateDirty;
191            final int otherPrivate = memInfo.otherPrivateDirty;
192
193            printRow(ps, formatString, "", "native", "dalvik", "other", "total");
194            printRow(ps, formatString, "size:", nativeMax, dalvikMax, "N/A", nativeMax + dalvikMax);
195            printRow(ps, formatString, "allocated:", nativeAllocated, dalvikAllocated, "N/A",
196                    nativeAllocated + dalvikAllocated);
197            printRow(ps, formatString, "free:", nativeFree, dalvikFree, "N/A",
198                    nativeFree + dalvikFree);
199
200            printRow(ps, formatString, "(Pss):", memInfo.nativePss, memInfo.dalvikPss,
201                    memInfo.otherPss, memInfo.nativePss + memInfo.dalvikPss + memInfo.otherPss);
202
203            printRow(ps, formatString, "(shared dirty):", nativeShared, dalvikShared, otherShared,
204                    nativeShared + dalvikShared + otherShared);
205            printRow(ps, formatString, "(priv dirty):", nativePrivate, dalvikPrivate, otherPrivate,
206                    nativePrivate + dalvikPrivate + otherPrivate);
207            ps.print("\n\n\n");
208            ps.flush();
209            ps.close();
210            out.flush();
211            out.close();
212        } catch (IOException e) {
213            Log.e(LOGTAG, e.getMessage());
214        }
215    }
216
217    // A convenient method to be called by another activity.
218    private void runTestAndWaitUntilDone(TestShellActivity activity, String url, int timeout) {
219        activity.setCallback(new TestShellCallback() {
220            @Override
221            public void finished() {
222                synchronized (LoadTestsAutoTest.this) {
223                    mFinished = true;
224                    LoadTestsAutoTest.this.notifyAll();
225                }
226            }
227
228            @Override
229            public void timedOut(String url) {
230            }
231
232            @Override
233            public void dumpResult(String webViewDump) {
234                String lines[] = webViewDump.split("\\r?\\n");
235                for (String line : lines) {
236                    line = line.trim();
237                    // parse for a line like this:
238                    // totals:   9620.00 11947.00    10099.75    380.38
239                    // and return the 3rd number, which is mean
240                    if (line.startsWith("totals:")) {
241                        line = line.substring(7).trim(); // strip "totals:"
242                        String[] numbers = line.split("\\s+");
243                        if (numbers.length == 4) {
244                            Bundle b = new Bundle();
245                            b.putString("mean", numbers[2]);
246                            getInstrumentation().sendStatus(Activity.RESULT_FIRST_USER, b);
247                        }
248                    }
249                }
250            }
251        });
252
253        mFinished = false;
254        Intent intent = new Intent(Intent.ACTION_VIEW);
255        intent.setClass(activity, TestShellActivity.class);
256        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
257        intent.putExtra(TestShellActivity.TEST_URL, url);
258        intent.putExtra(TestShellActivity.TIMEOUT_IN_MILLIS, timeout);
259        intent.putExtra(TestShellActivity.RESULT_FILE, LOAD_TEST_RESULT);
260        activity.startActivity(intent);
261
262        // Wait until done.
263        synchronized (this) {
264            while(!mFinished) {
265                try {
266                    this.wait();
267                } catch (InterruptedException e) { }
268            }
269        }
270    }
271
272    public void copyRunnerAssetsToCache() {
273        try {
274            Context targetContext = getInstrumentation().getTargetContext();
275            File cacheDir = targetContext.getCacheDir();
276
277            for( int i=0; i< LOAD_TEST_RUNNER_FILES.length; i++) {
278                InputStream in = targetContext.getAssets().open(
279                        LOAD_TEST_RUNNER_FILES[i]);
280                OutputStream out = new FileOutputStream(
281                        new File(cacheDir, LOAD_TEST_RUNNER_FILES[i]));
282
283                byte[] buf = new byte[2048];
284                int len;
285
286                while ((len = in.read(buf)) >= 0 ) {
287                    out.write(buf, 0, len);
288                }
289                out.close();
290                in.close();
291            }
292        }catch (IOException e) {
293          e.printStackTrace();
294        }
295
296    }
297
298}
299