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