1 /* 2 * Copyright (C) 2014 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 17 package android.support.test.aupt; 18 19 import java.io.BufferedReader; 20 import java.io.BufferedWriter; 21 import java.io.ByteArrayOutputStream; 22 import java.io.File; 23 import java.io.FileInputStream; 24 import java.io.FileNotFoundException; 25 import java.io.FileOutputStream; 26 import java.io.FileWriter; 27 import java.io.IOException; 28 import java.io.InputStreamReader; 29 import java.io.PrintWriter; 30 import java.util.ArrayList; 31 import java.util.Collection; 32 import java.util.HashMap; 33 import java.util.List; 34 import java.util.Map; 35 import java.util.regex.Matcher; 36 import java.util.regex.Pattern; 37 38 import android.accounts.Account; 39 import android.accounts.AccountManager; 40 import android.content.Context; 41 import android.content.Intent; 42 import android.content.pm.PackageInfo; 43 import android.content.pm.PackageManager; 44 import android.content.pm.PackageManager.NameNotFoundException; 45 import android.os.Bundle; 46 import android.os.Environment; 47 import android.os.SystemClock; 48 import android.support.test.uiautomator.By; 49 import android.support.test.uiautomator.UiDevice; 50 import android.support.test.uiautomator.UiWatcher; 51 import android.test.InstrumentationTestCase; 52 import android.test.InstrumentationTestRunner; 53 import android.util.Log; 54 55 import junit.framework.Assert; 56 57 /** 58 * Base class for AuptTests. 59 */ 60 public class AuptTestCase extends InstrumentationTestCase { 61 private static final String SDCARD = 62 Environment.getExternalStorageDirectory().getAbsolutePath(); 63 private static final String RECORD_MEMINFO_PARAM = "record_meminfo"; 64 private static final long DEFAULT_SHORT_SLEEP = 5 * 1000; 65 private static final long DEFAULT_LONG_SLEEP = 30 * 1000; 66 protected static final int STEPS_BACK = 10; 67 protected static final int RECOVERY_SLEEP = 2000; 68 private static final String TAG = AuptTestCase.class.getSimpleName(); 69 70 private boolean mRecordMeminfo = false; 71 private UiWatchers mWatchers; 72 private IProcessStatusTracker mProcessStatusTracker; 73 private DataCollector mDataCollector; 74 private List<String> mPaths; 75 76 // We want to periodically collect dumpheap output if the process grows to large to proactivelly 77 // help with catching memory leaks, but don't want to do it too often so it does not disturb the 78 // test. We are going to limit the total number of dumpheap commands per proccess to 5 by 79 // default, rate limit it to one per hour be default, and only do it for monitored processes 80 // which grow larger than a certain size (200MB by default). 81 private boolean mDumpheapEnabled = false; 82 private long mDumpheapThreshold; 83 private long mDumpheapInterval ; 84 private long mMaxDumpheaps; 85 private Map<String, Long> mLastDumpheap = new HashMap<String, Long>(); 86 private Map<String, Long> mDumpheapCount = new HashMap<String, Long>(); 87 88 private UiDevice mDevice; 89 90 public static class MemHealthRecord { 91 public enum Context { FOREGROUND, BACKGROUND }; 92 93 private final long mTimeMs; 94 private final long mDalvikHeap; 95 private final long mNativeHeap; 96 private final long mPss; 97 98 // App summary metrics 99 private final long mAsJavaHeap; // Private java heap 100 private final long mAsNativeHeap; // Private native heap 101 private final long mAsCode; // Private code 102 private final long mAsStack; // Private stack 103 private final long mAsGraphics; // Private graphics 104 private final long mAsOther; // Private other 105 private final long mAsSystem; // System 106 private final long mAsOverallPss; // Overall PSS 107 108 private final Context mContext; 109 MemHealthRecord(long timeMs, long dalvikHeap, long nativeHeap, long pss, long asJavaHeap, long asNativeHeap, long asCode, long asStack, long asGraphics, long asOther, long asSystem, long asOverallPss, Context context)110 public MemHealthRecord(long timeMs, long dalvikHeap, long nativeHeap, long pss, 111 long asJavaHeap, long asNativeHeap, long asCode, long asStack, 112 long asGraphics, long asOther, long asSystem, long asOverallPss, 113 Context context) { 114 mTimeMs = timeMs; 115 mDalvikHeap = dalvikHeap; 116 mNativeHeap = nativeHeap; 117 mPss = pss; 118 mAsJavaHeap = asJavaHeap; 119 mAsNativeHeap = asNativeHeap; 120 mAsCode = asCode; 121 mAsStack = asStack; 122 mAsGraphics = asGraphics; 123 mAsOther = asOther; 124 mAsSystem = asSystem; 125 mAsOverallPss = asOverallPss; 126 mContext = context; 127 } 128 MemHealthRecord(long timeMs, long dalvikHeap, long nativeHeap, long pss, Context context)129 public MemHealthRecord(long timeMs, long dalvikHeap, long nativeHeap, long pss, 130 Context context) { 131 mTimeMs = timeMs; 132 mDalvikHeap = dalvikHeap; 133 mNativeHeap = nativeHeap; 134 mPss = pss; 135 mAsJavaHeap = 0; 136 mAsNativeHeap = 0; 137 mAsCode = 0; 138 mAsStack = 0; 139 mAsGraphics = 0; 140 mAsOther = 0; 141 mAsSystem = 0; 142 mAsOverallPss = 0; 143 mContext = context; 144 } 145 getForegroundDalvikHeap(Collection<MemHealthRecord> samples)146 public static List<Long> getForegroundDalvikHeap(Collection<MemHealthRecord> samples) { 147 List<Long> ret = new ArrayList<>(samples.size()); 148 for (MemHealthRecord sample : samples) { 149 if (Context.FOREGROUND.equals(sample.mContext)) { 150 ret.add(sample.mDalvikHeap); 151 } 152 } 153 return ret; 154 } 155 getBackgroundDalvikHeap(Collection<MemHealthRecord> samples)156 public static List<Long> getBackgroundDalvikHeap(Collection<MemHealthRecord> samples) { 157 List<Long> ret = new ArrayList<>(samples.size()); 158 for (MemHealthRecord sample : samples) { 159 if (Context.BACKGROUND.equals(sample.mContext)) { 160 ret.add(sample.mDalvikHeap); 161 } 162 } 163 return ret; 164 } 165 getForegroundNativeHeap(Collection<MemHealthRecord> samples)166 public static List<Long> getForegroundNativeHeap(Collection<MemHealthRecord> samples) { 167 List<Long> ret = new ArrayList<>(samples.size()); 168 for (MemHealthRecord sample : samples) { 169 if (Context.FOREGROUND.equals(sample.mContext)) { 170 ret.add(sample.mNativeHeap); 171 } 172 } 173 return ret; 174 } 175 getBackgroundNativeHeap(Collection<MemHealthRecord> samples)176 public static List<Long> getBackgroundNativeHeap(Collection<MemHealthRecord> samples) { 177 List<Long> ret = new ArrayList<>(samples.size()); 178 for (MemHealthRecord sample : samples) { 179 if (Context.BACKGROUND.equals(sample.mContext)) { 180 ret.add(sample.mNativeHeap); 181 } 182 } 183 return ret; 184 } 185 getForegroundPss(Collection<MemHealthRecord> samples)186 public static List<Long> getForegroundPss(Collection<MemHealthRecord> samples) { 187 List<Long> ret = new ArrayList<>(samples.size()); 188 for (MemHealthRecord sample : samples) { 189 if (Context.FOREGROUND.equals(sample.mContext)) { 190 ret.add(sample.mPss); 191 } 192 } 193 return ret; 194 } 195 getBackgroundPss(Collection<MemHealthRecord> samples)196 public static List<Long> getBackgroundPss(Collection<MemHealthRecord> samples) { 197 List<Long> ret = new ArrayList<>(samples.size()); 198 for (MemHealthRecord sample : samples) { 199 if (Context.BACKGROUND.equals(sample.mContext)) { 200 ret.add(sample.mPss); 201 } 202 } 203 return ret; 204 } 205 getForegroundSummaryJavaHeap(Collection<MemHealthRecord> samples)206 public static List<Long> getForegroundSummaryJavaHeap(Collection<MemHealthRecord> samples) { 207 List<Long> ret = new ArrayList<>(samples.size()); 208 for (MemHealthRecord sample : samples) { 209 if (Context.FOREGROUND.equals(sample.mContext)) { 210 ret.add(sample.mAsJavaHeap); 211 } 212 } 213 return ret; 214 } 215 getBackgroundSummaryJavaHeap(Collection<MemHealthRecord> samples)216 public static List<Long> getBackgroundSummaryJavaHeap(Collection<MemHealthRecord> samples) { 217 List<Long> ret = new ArrayList<>(samples.size()); 218 for (MemHealthRecord sample : samples) { 219 if (Context.BACKGROUND.equals(sample.mContext)) { 220 ret.add(sample.mAsJavaHeap); 221 } 222 } 223 return ret; 224 } 225 getForegroundSummaryNativeHeap( Collection<MemHealthRecord> samples)226 public static List<Long> getForegroundSummaryNativeHeap( 227 Collection<MemHealthRecord> samples) { 228 List<Long> ret = new ArrayList<>(samples.size()); 229 for (MemHealthRecord sample : samples) { 230 if (Context.FOREGROUND.equals(sample.mContext)) { 231 ret.add(sample.mAsNativeHeap); 232 } 233 } 234 return ret; 235 } 236 getBackgroundSummaryNativeHeap( Collection<MemHealthRecord> samples)237 public static List<Long> getBackgroundSummaryNativeHeap( 238 Collection<MemHealthRecord> samples) { 239 List<Long> ret = new ArrayList<>(samples.size()); 240 for (MemHealthRecord sample : samples) { 241 if (Context.BACKGROUND.equals(sample.mContext)) { 242 ret.add(sample.mAsNativeHeap); 243 } 244 } 245 return ret; 246 } 247 getForegroundSummaryCode(Collection<MemHealthRecord> samples)248 public static List<Long> getForegroundSummaryCode(Collection<MemHealthRecord> samples) { 249 List<Long> ret = new ArrayList<>(samples.size()); 250 for (MemHealthRecord sample : samples) { 251 if (Context.FOREGROUND.equals(sample.mContext)) { 252 ret.add(sample.mAsCode); 253 } 254 } 255 return ret; 256 } 257 getBackgroundSummaryCode(Collection<MemHealthRecord> samples)258 public static List<Long> getBackgroundSummaryCode(Collection<MemHealthRecord> samples) { 259 List<Long> ret = new ArrayList<>(samples.size()); 260 for (MemHealthRecord sample : samples) { 261 if (Context.BACKGROUND.equals(sample.mContext)) { 262 ret.add(sample.mAsCode); 263 } 264 } 265 return ret; 266 } 267 getForegroundSummaryStack(Collection<MemHealthRecord> samples)268 public static List<Long> getForegroundSummaryStack(Collection<MemHealthRecord> samples) { 269 List<Long> ret = new ArrayList<>(samples.size()); 270 for (MemHealthRecord sample : samples) { 271 if (Context.FOREGROUND.equals(sample.mContext)) { 272 ret.add(sample.mAsStack); 273 } 274 } 275 return ret; 276 } 277 getBackgroundSummaryStack(Collection<MemHealthRecord> samples)278 public static List<Long> getBackgroundSummaryStack(Collection<MemHealthRecord> samples) { 279 List<Long> ret = new ArrayList<>(samples.size()); 280 for (MemHealthRecord sample : samples) { 281 if (Context.BACKGROUND.equals(sample.mContext)) { 282 ret.add(sample.mAsStack); 283 } 284 } 285 return ret; 286 } 287 getForegroundSummaryGraphics(Collection<MemHealthRecord> samples)288 public static List<Long> getForegroundSummaryGraphics(Collection<MemHealthRecord> samples) { 289 List<Long> ret = new ArrayList<>(samples.size()); 290 for (MemHealthRecord sample : samples) { 291 if (Context.FOREGROUND.equals(sample.mContext)) { 292 ret.add(sample.mAsGraphics); 293 } 294 } 295 return ret; 296 } 297 getBackgroundSummaryGraphics(Collection<MemHealthRecord> samples)298 public static List<Long> getBackgroundSummaryGraphics(Collection<MemHealthRecord> samples) { 299 List<Long> ret = new ArrayList<>(samples.size()); 300 for (MemHealthRecord sample : samples) { 301 if (Context.BACKGROUND.equals(sample.mContext)) { 302 ret.add(sample.mAsGraphics); 303 } 304 } 305 return ret; 306 } 307 getForegroundSummaryOther(Collection<MemHealthRecord> samples)308 public static List<Long> getForegroundSummaryOther(Collection<MemHealthRecord> samples) { 309 List<Long> ret = new ArrayList<>(samples.size()); 310 for (MemHealthRecord sample : samples) { 311 if (Context.FOREGROUND.equals(sample.mContext)) { 312 ret.add(sample.mAsOther); 313 } 314 } 315 return ret; 316 } 317 getBackgroundSummaryOther(Collection<MemHealthRecord> samples)318 public static List<Long> getBackgroundSummaryOther(Collection<MemHealthRecord> samples) { 319 List<Long> ret = new ArrayList<>(samples.size()); 320 for (MemHealthRecord sample : samples) { 321 if (Context.BACKGROUND.equals(sample.mContext)) { 322 ret.add(sample.mAsOther); 323 } 324 } 325 return ret; 326 } 327 getForegroundSummarySystem(Collection<MemHealthRecord> samples)328 public static List<Long> getForegroundSummarySystem(Collection<MemHealthRecord> samples) { 329 List<Long> ret = new ArrayList<>(samples.size()); 330 for (MemHealthRecord sample : samples) { 331 if (Context.FOREGROUND.equals(sample.mContext)) { 332 ret.add(sample.mAsSystem); 333 } 334 } 335 return ret; 336 } 337 getBackgroundSummarySystem(Collection<MemHealthRecord> samples)338 public static List<Long> getBackgroundSummarySystem(Collection<MemHealthRecord> samples) { 339 List<Long> ret = new ArrayList<>(samples.size()); 340 for (MemHealthRecord sample : samples) { 341 if (Context.BACKGROUND.equals(sample.mContext)) { 342 ret.add(sample.mAsSystem); 343 } 344 } 345 return ret; 346 } 347 getForegroundSummaryOverallPss( Collection<MemHealthRecord> samples)348 public static List<Long> getForegroundSummaryOverallPss( 349 Collection<MemHealthRecord> samples) { 350 List<Long> ret = new ArrayList<>(samples.size()); 351 for (MemHealthRecord sample : samples) { 352 if (Context.FOREGROUND.equals(sample.mContext)) { 353 ret.add(sample.mAsOverallPss); 354 } 355 } 356 return ret; 357 } 358 getBackgroundSummaryOverallPss( Collection<MemHealthRecord> samples)359 public static List<Long> getBackgroundSummaryOverallPss( 360 Collection<MemHealthRecord> samples) { 361 List<Long> ret = new ArrayList<>(samples.size()); 362 for (MemHealthRecord sample : samples) { 363 if (Context.BACKGROUND.equals(sample.mContext)) { 364 ret.add(sample.mAsOverallPss); 365 } 366 } 367 return ret; 368 } 369 getMax(Collection<Long> samples)370 private static Long getMax(Collection<Long> samples) { 371 Long max = null; 372 for (Long sample : samples) { 373 if (max == null || sample > max) { 374 max = sample; 375 } 376 } 377 return max; 378 } 379 getAverage(Collection<Long> samples)380 private static Long getAverage(Collection<Long> samples) { 381 if (samples.size() == 0) { 382 return null; 383 } 384 385 double sum = 0; 386 for (Long sample : samples) { 387 sum += sample; 388 } 389 return (long) (sum / samples.size()); 390 } 391 } 392 393 private Map<String, List<MemHealthRecord>> mMemHealthRecords; 394 private String[] mProcsToTrack; 395 private String mResultsDirectory; 396 setMemHealthRecords(Map<String, List<MemHealthRecord>> records)397 public void setMemHealthRecords(Map<String, List<MemHealthRecord>> records) { 398 mMemHealthRecords = records; 399 } 400 401 @Override setUp()402 protected void setUp() throws Exception { 403 super.setUp(); 404 405 mDevice = UiDevice.getInstance(getInstrumentation()); 406 mWatchers = new UiWatchers(); 407 mWatchers.registerAnrAndCrashWatchers(getInstrumentation()); 408 mDevice.registerWatcher("LockScreenWatcher", new LockScreenWatcher()); 409 mRecordMeminfo = "true".equals(getParams().getString(RECORD_MEMINFO_PARAM, "false")); 410 411 mDevice.setOrientationNatural(); 412 413 mResultsDirectory = SDCARD + "/" + getParams().getString( 414 "outputLocation", "aupt_results"); 415 416 String processes = getParams().getString("trackMemory", null); 417 if (processes != null) { 418 mProcsToTrack = processes.split(","); 419 } else { 420 readProcessesFromFile(); 421 } 422 423 mDumpheapEnabled = "true".equals(getParams().getString("enableDumpheap")); 424 if (mDumpheapEnabled) { 425 mDumpheapThreshold = getLongParam("dumpheapThreshold", 200 * 1024 * 1024); // 200MB 426 mDumpheapInterval = getLongParam("dumpheapInterval", 60 * 60 * 1000); // one hour 427 mMaxDumpheaps = getLongParam("maxDumpheaps", 5); 428 } 429 } 430 readProcessesFromFile()431 private void readProcessesFromFile() { 432 File trackFile = new File(SDCARD + "/track_memory.txt"); 433 if (trackFile.exists()) { 434 BufferedReader in = null; 435 try { 436 in = new BufferedReader(new InputStreamReader(new FileInputStream(trackFile))); 437 String processes = in.readLine(); 438 in.close(); 439 440 if (!"".equals(processes)) { 441 mProcsToTrack = processes.split(","); 442 } 443 } catch (FileNotFoundException e) { 444 Log.e(TAG, "Error opening track file", e); 445 } catch (IOException e) { 446 Log.e(TAG, "Error opening track file", e); 447 } 448 } 449 } 450 451 /** 452 * {@inheritDoc} 453 */ 454 @Override tearDown()455 protected void tearDown() throws Exception { 456 mDevice.removeWatcher("LockScreenWatcher"); 457 mDevice.unfreezeRotation(); 458 459 saveMemoryStats(); 460 461 super.tearDown(); 462 } 463 464 private class LockScreenWatcher implements UiWatcher { 465 466 @Override checkForCondition()467 public boolean checkForCondition() { 468 if (mDevice.hasObject(By.desc("Slide area."))) { 469 mDevice.pressMenu(); 470 return true; 471 } 472 return false; 473 } 474 } 475 476 /** 477 * Looks up a parameter or returns a default value if parameter is not 478 * present. 479 * @param key 480 * @param defaultValue 481 * @return passed in parameter or default value if parameter is not found. 482 */ getLongParam(String key, long defaultValue)483 public long getLongParam(String key, long defaultValue) throws NumberFormatException { 484 if (getParams().containsKey(key)) { 485 return Long.parseLong(getParams().getString(key)); 486 } else { 487 return defaultValue; 488 } 489 } 490 491 /** 492 * Returns the timeout for short sleep. Can be set with shortSleep command 493 * line option. Default is 5 seconds. 494 * @return time in milliseconds 495 */ getShortSleep()496 public long getShortSleep() { 497 return getLongParam("shortSleep", DEFAULT_SHORT_SLEEP); 498 } 499 500 /** 501 * Returns the timeout for long sleep. Can be set with longSleep command 502 * line option. Default is 30 seconds 503 * @return time in milliseconds. 504 */ getLongSleep()505 public long getLongSleep() { 506 return getLongParam("longSleep", DEFAULT_LONG_SLEEP); 507 } 508 509 /** 510 * Press back button repeatedly in order to attempt to bring the app back to home screen. 511 * This is intended so that an app can recover if the previous session left an app in a weird 512 * state. 513 */ navigateToHome()514 public void navigateToHome() { 515 int iterations = 0; 516 String launcherPkg = mDevice.getLauncherPackageName(); 517 while (!launcherPkg.equals(mDevice.getCurrentPackageName()) 518 && iterations < STEPS_BACK) { 519 mDevice.pressBack(); 520 SystemClock.sleep(RECOVERY_SLEEP); 521 iterations++; 522 } 523 } 524 525 /** 526 * Writes out condensed memory data about the running processes. 527 * @param notes about when the dump was taken. 528 */ dumpMemInfo(String notes)529 public void dumpMemInfo(String notes) { 530 if (mRecordMeminfo) { 531 mDevice.waitForIdle(); 532 mDataCollector.dumpMeminfo(notes); 533 } 534 if (mProcsToTrack != null) { 535 recordMemoryUsage(); 536 } 537 } 538 saveMemoryStats()539 private void saveMemoryStats() { 540 if (mProcsToTrack == null) { 541 return; 542 } 543 try { 544 PrintWriter out = new PrintWriter(new BufferedWriter( 545 new FileWriter(mResultsDirectory + "/memory-health.txt"))); 546 out.println("Foreground"); 547 for (Map.Entry<String, List<MemHealthRecord>> record : mMemHealthRecords.entrySet()) { 548 List<Long> nativeHeap = MemHealthRecord.getForegroundNativeHeap(record.getValue()); 549 List<Long> dalvikHeap = MemHealthRecord.getForegroundDalvikHeap(record.getValue()); 550 List<Long> pss = MemHealthRecord.getForegroundPss(record.getValue()); 551 552 List<Long> asJavaHeap = MemHealthRecord.getForegroundSummaryJavaHeap( 553 record.getValue()); 554 List<Long> asNativeHeap = MemHealthRecord.getForegroundSummaryNativeHeap( 555 record.getValue()); 556 List<Long> asCode = MemHealthRecord.getForegroundSummaryCode(record.getValue()); 557 List<Long> asStack = MemHealthRecord.getForegroundSummaryStack(record.getValue()); 558 List<Long> asGraphics = MemHealthRecord.getForegroundSummaryGraphics( 559 record.getValue()); 560 List<Long> asOther = MemHealthRecord.getForegroundSummaryOther(record.getValue()); 561 List<Long> asSystem = MemHealthRecord.getForegroundSummarySystem(record.getValue()); 562 List<Long> asOverallPss = MemHealthRecord.getForegroundSummaryOverallPss( 563 record.getValue()); 564 565 // nativeHeap, dalvikHeap, and pss all have the same size, just use one 566 if (nativeHeap.size() == 0) { 567 continue; 568 } 569 570 out.println(record.getKey()); 571 out.printf("Average Native Heap: %d\n", MemHealthRecord.getAverage(nativeHeap)); 572 out.printf("Average Dalvik Heap: %d\n", MemHealthRecord.getAverage(dalvikHeap)); 573 out.printf("Average PSS: %d\n", MemHealthRecord.getAverage(pss)); 574 out.printf("Peak Native Heap: %d\n", MemHealthRecord.getMax(nativeHeap)); 575 out.printf("Peak Dalvik Heap: %d\n", MemHealthRecord.getMax(dalvikHeap)); 576 out.printf("Peak PSS: %d\n", MemHealthRecord.getMax(pss)); 577 out.printf("Count %d\n", nativeHeap.size()); 578 579 out.printf("Average Summary Java Heap: %d\n", MemHealthRecord.getAverage( 580 asJavaHeap)); 581 out.printf("Average Summary Native Heap: %d\n", MemHealthRecord.getAverage( 582 asNativeHeap)); 583 out.printf("Average Summary Code: %d\n", MemHealthRecord.getAverage(asCode)); 584 out.printf("Average Summary Stack: %d\n", MemHealthRecord.getAverage(asStack)); 585 out.printf("Average Summary Graphics: %d\n", MemHealthRecord.getAverage( 586 asGraphics)); 587 out.printf("Average Summary Other: %d\n", MemHealthRecord.getAverage(asOther)); 588 out.printf("Average Summary System: %d\n", MemHealthRecord.getAverage(asSystem)); 589 out.printf("Average Summary Overall Pss: %d\n", MemHealthRecord.getAverage( 590 asOverallPss)); 591 } 592 out.println("Background"); 593 for (Map.Entry<String, List<MemHealthRecord>> record : mMemHealthRecords.entrySet()) { 594 List<Long> nativeHeap = MemHealthRecord.getBackgroundNativeHeap(record.getValue()); 595 List<Long> dalvikHeap = MemHealthRecord.getBackgroundDalvikHeap(record.getValue()); 596 List<Long> pss = MemHealthRecord.getBackgroundPss(record.getValue()); 597 598 List<Long> asJavaHeap = MemHealthRecord.getBackgroundSummaryJavaHeap( 599 record.getValue()); 600 List<Long> asNativeHeap = MemHealthRecord.getBackgroundSummaryNativeHeap( 601 record.getValue()); 602 List<Long> asCode = MemHealthRecord.getBackgroundSummaryCode(record.getValue()); 603 List<Long> asStack = MemHealthRecord.getBackgroundSummaryStack(record.getValue()); 604 List<Long> asGraphics = MemHealthRecord.getBackgroundSummaryGraphics( 605 record.getValue()); 606 List<Long> asOther = MemHealthRecord.getBackgroundSummaryOther(record.getValue()); 607 List<Long> asSystem = MemHealthRecord.getBackgroundSummarySystem(record.getValue()); 608 List<Long> asOverallPss = MemHealthRecord.getBackgroundSummaryOverallPss( 609 record.getValue()); 610 611 // nativeHeap, dalvikHeap, and pss all have the same size, just use one 612 if (nativeHeap.size() == 0) { 613 continue; 614 } 615 616 out.println(record.getKey()); 617 out.printf("Average Native Heap: %d\n", MemHealthRecord.getAverage(nativeHeap)); 618 out.printf("Average Dalvik Heap: %d\n", MemHealthRecord.getAverage(dalvikHeap)); 619 out.printf("Average PSS: %d\n", MemHealthRecord.getAverage(pss)); 620 out.printf("Peak Native Heap: %d\n", MemHealthRecord.getMax(nativeHeap)); 621 out.printf("Peak Dalvik Heap: %d\n", MemHealthRecord.getMax(dalvikHeap)); 622 out.printf("Peak PSS: %d\n", MemHealthRecord.getMax(pss)); 623 out.printf("Count %d\n", nativeHeap.size()); 624 625 out.printf("Average Summary Java Heap: %d\n", MemHealthRecord.getAverage( 626 asJavaHeap)); 627 out.printf("Average Summary Native Heap: %d\n", MemHealthRecord.getAverage( 628 asNativeHeap)); 629 out.printf("Average Summary Code: %d\n", MemHealthRecord.getAverage(asCode)); 630 out.printf("Average Summary Stack: %d\n", MemHealthRecord.getAverage(asStack)); 631 out.printf("Average Summary Graphics: %d\n", MemHealthRecord.getAverage( 632 asGraphics)); 633 out.printf("Average Summary Other: %d\n", MemHealthRecord.getAverage(asOther)); 634 out.printf("Average Summary System: %d\n", MemHealthRecord.getAverage(asSystem)); 635 out.printf("Average Summary Overall Pss: %d\n", MemHealthRecord.getAverage( 636 asOverallPss)); 637 } 638 out.close(); 639 } catch (IOException e) { 640 Log.e(TAG, "Error while saving memory stats", e); 641 } 642 643 // Temporary hack to write full logs 644 try { 645 PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter( 646 mResultsDirectory + "/memory-health-details.txt"))); 647 for (Map.Entry<String, List<MemHealthRecord>> record : mMemHealthRecords.entrySet()) { 648 out.println(record.getKey()); 649 out.printf("time,native_heap,dalvik_heap,pss,context\n"); 650 for (MemHealthRecord sample : record.getValue()) { 651 out.printf("%d,%d,%d,%s\n", sample.mTimeMs, sample.mNativeHeap, 652 sample.mDalvikHeap, sample.mContext.toString().toLowerCase()); 653 } 654 } 655 out.close(); 656 } catch (IOException e) { 657 Log.e(TAG, "Error while saving memory stat details", e); 658 } 659 } 660 recordMemoryUsage()661 private void recordMemoryUsage() { 662 if (mProcsToTrack == null) { 663 return; 664 } 665 long timeMs = System.currentTimeMillis(); 666 List<String> foregroundProcs = getForegroundProc(); 667 for (String proc : mProcsToTrack) { 668 recordMemoryUsage(proc, timeMs, foregroundProcs); 669 } 670 } 671 recordMemoryUsage(String proc, long timeMs, List<String> foregroundProcs)672 private void recordMemoryUsage(String proc, long timeMs, List<String> foregroundProcs) { 673 try { 674 String meminfo = getMeminfoOutput(proc); 675 int nativeHeap = parseMeminfoLine(meminfo, "Native Heap\\s+\\d+\\s+(\\d+)"); 676 int dalvikHeap = parseMeminfoLine(meminfo, "Dalvik Heap\\s+\\d+\\s+(\\d+)"); 677 int pss = parseMeminfoLine(meminfo, "TOTAL\\s+(\\d+)"); 678 679 int asJavaHeap = parseMeminfoLine(meminfo, "Java Heap:\\s+(\\d+)"); 680 int asNativeHeap = parseMeminfoLine(meminfo, "Native Heap:\\s+(\\d+)"); 681 int asCode = parseMeminfoLine(meminfo, "Code:\\s+(\\d+)"); 682 int asStack = parseMeminfoLine(meminfo, "Stack:\\s+(\\d+)"); 683 int asGraphics = parseMeminfoLine(meminfo, "Graphics:\\s+(\\d+)"); 684 int asOther = parseMeminfoLine(meminfo, "Private Other:\\s+(\\d+)"); 685 int asSystem = parseMeminfoLine(meminfo, "System:\\s+(\\d+)"); 686 int asOverallPss = parseMeminfoLine(meminfo, "TOTAL:\\s+(\\d+)"); 687 688 if (nativeHeap < 0 || dalvikHeap < 0 || pss < 0) { 689 return; 690 } 691 MemHealthRecord.Context context = foregroundProcs.contains(proc) ? 692 MemHealthRecord.Context.FOREGROUND : MemHealthRecord.Context.BACKGROUND; 693 if (!mMemHealthRecords.containsKey(proc)) { 694 mMemHealthRecords.put(proc, new ArrayList<MemHealthRecord>()); 695 } 696 mMemHealthRecords.get(proc).add( 697 new MemHealthRecord(timeMs, dalvikHeap, nativeHeap, pss, asJavaHeap, 698 asNativeHeap, asCode, asStack, asGraphics, asOther, asSystem, 699 asOverallPss, context)); 700 recordDumpheap(proc, pss); 701 } catch (IOException e) { 702 Log.e(TAG, "exception while memory stats", e); 703 } 704 } 705 parseMeminfoLine(String meminfo, String pattern)706 private int parseMeminfoLine(String meminfo, String pattern) 707 { 708 Pattern p = Pattern.compile(pattern); 709 Matcher m = p.matcher(meminfo); 710 if (m.find()) { 711 return Integer.parseInt(m.group(1)); 712 } else { 713 return -1; 714 } 715 } 716 getMeminfoOutput(String processName)717 private String getMeminfoOutput(String processName) throws IOException { 718 return getProcessOutput("dumpsys meminfo " + processName); 719 } 720 recordDumpheap(String proc, long pss)721 private void recordDumpheap(String proc, long pss) throws IOException { 722 if (!mDumpheapEnabled) { 723 return; 724 } 725 Long count = mDumpheapCount.get(proc); 726 if (count == null) { 727 count = 0L; 728 } 729 Long lastDumpheap = mLastDumpheap.get(proc); 730 if (lastDumpheap == null) { 731 lastDumpheap = 0L; 732 } 733 long currentTime = SystemClock.uptimeMillis(); 734 if (pss > mDumpheapThreshold && count < mMaxDumpheaps && 735 currentTime - lastDumpheap > mDumpheapInterval) { 736 recordDumpheap(proc); 737 mDumpheapCount.put(proc, count + 1); 738 mLastDumpheap.put(proc, currentTime); 739 } 740 } 741 recordDumpheap(String proc)742 private void recordDumpheap(String proc) throws IOException { 743 // Turns out getting dumpheap output is non-trivial. The command runs as shell user, and 744 // only has access to /data/local/tmp directory to write files to. The test does not have 745 // access to the output file by default because of the permissions dumpheap sets. So we need 746 // to run dumpheap, change permissions on the output file and copy it to where test harness 747 // can pick it up. 748 Long count = mDumpheapCount.get(proc); 749 if (count == null) { 750 count = 0L; 751 } 752 String filename = String.format("dumpheap-%s-%d", proc, count); 753 String tempFilename = "/data/local/tmp/" + filename; 754 String finalFilename = mResultsDirectory +"/" + filename; 755 String command = String.format("am dumpheap %s %s", proc, tempFilename); 756 getProcessOutput(command); 757 SystemClock.sleep(3000); 758 getProcessOutput(String.format("cp %s %s", tempFilename, finalFilename)); 759 } 760 getProcessOutput(String command)761 public String getProcessOutput(String command) throws IOException { 762 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 763 mDataCollector.saveProcessOutput(command, baos); 764 baos.close(); 765 return baos.toString(); 766 } 767 getForegroundProc()768 private List<String> getForegroundProc() { 769 List<String> foregroundProcs = new ArrayList<String>(); 770 try { 771 String compactMeminfo = getProcessOutput("dumpsys meminfo -c"); 772 for (String line : compactMeminfo.split("\\r?\\n")) { 773 if (line.contains("proc,fore")) { 774 String proc = line.split(",")[2]; 775 foregroundProcs.add(proc); 776 } 777 } 778 } catch (IOException e) { 779 Log.e(TAG, "Error while getting foreground process", e); 780 } finally { 781 return foregroundProcs; 782 } 783 } 784 setProcessStatusTracker(IProcessStatusTracker processStatusTracker)785 public void setProcessStatusTracker(IProcessStatusTracker processStatusTracker) { 786 mProcessStatusTracker = processStatusTracker; 787 } 788 getProcessStatusTracker()789 public IProcessStatusTracker getProcessStatusTracker() { 790 return mProcessStatusTracker; 791 } 792 launchIntent(Intent intent)793 public void launchIntent(Intent intent) { 794 getInstrumentation().getContext().startActivity(intent); 795 } 796 getParams()797 protected Bundle getParams() { 798 return ((InstrumentationTestRunner)getInstrumentation()).getArguments(); 799 } 800 getUiDevice()801 protected UiDevice getUiDevice() { 802 return mDevice; 803 } 804 setDataCollector(DataCollector collector)805 public void setDataCollector(DataCollector collector) { 806 mDataCollector = collector; 807 } 808 setDexedJarPaths(List<String> paths)809 public void setDexedJarPaths(List<String> paths) { 810 mPaths = paths; 811 } 812 getDexedJarPaths()813 public List<String> getDexedJarPaths() { 814 return mPaths; 815 } 816 getPackageVersion(String packageName)817 public String getPackageVersion(String packageName) throws NameNotFoundException { 818 if (null == packageName || packageName.isEmpty()) { 819 throw new RuntimeException("Package name can't be null or empty"); 820 } 821 PackageManager pm = getInstrumentation().getContext().getPackageManager(); 822 PackageInfo pInfo = pm.getPackageInfo(packageName, 0); 823 String version = pInfo.versionName; 824 if (null == version || version.isEmpty()) { 825 throw new RuntimeException( 826 String.format("Version isn't found for package = %s", packageName)); 827 } 828 829 return version; 830 } 831 832 /** 833 * Get registered accounts 834 * Ensures there is at least one account registered 835 * returns the google account name 836 */ getRegisteredEmailAccount()837 public String getRegisteredEmailAccount() { 838 Account[] accounts = AccountManager.get(getInstrumentation().getContext()).getAccounts(); 839 Assert.assertTrue("Device doesn't have any account registered", accounts.length >= 1); 840 for(int i =0; i < accounts.length; ++i) { 841 if(accounts[i].type.equals("com.google")) { 842 return accounts[i].name; 843 } 844 } 845 846 throw new RuntimeException("The device is not registered with a google account"); 847 } 848 } 849