1 /* 2 * Copyright (C) 2016 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 android.app.Instrumentation; 20 import java.io.BufferedWriter; 21 import java.io.ByteArrayOutputStream; 22 import java.io.FileWriter; 23 import java.io.IOException; 24 import java.io.PrintWriter; 25 import java.util.ArrayList; 26 import java.util.Collection; 27 import java.util.HashMap; 28 import java.util.List; 29 import java.util.Map; 30 import java.util.regex.Matcher; 31 import java.util.regex.Pattern; 32 33 class MemHealthRecord { 34 // Process State 35 private final String mProcName; 36 private final boolean mInForeground; 37 38 // Memory health state 39 private final long mTimeMs; 40 private final long mDalvikHeap; 41 private final long mNativeHeap; 42 private final long mPss; 43 44 // App summary metrics 45 private final long mAsJavaHeap; 46 private final long mAsNativeHeap; 47 private final long mAsCode; 48 private final long mAsStack; 49 private final long mAsGraphics; 50 private final long mAsOther; 51 private final long mAsSystem; 52 private final long mAsOverallPss; 53 MemHealthRecord(String procName, long timeMs, long dalvikHeap, long nativeHeap, long pss, long asJavaHeap, long asNativeHeap, long asCode, long asStack, long asGraphics, long asOther, long asSystem, long asOverallPss, boolean inForeground)54 public MemHealthRecord(String procName, long timeMs, long dalvikHeap, long nativeHeap, long pss, 55 long asJavaHeap, long asNativeHeap, long asCode, long asStack, 56 long asGraphics, long asOther, long asSystem, long asOverallPss, 57 boolean inForeground) { 58 mProcName = procName; 59 mTimeMs = timeMs; 60 mDalvikHeap = dalvikHeap; 61 mNativeHeap = nativeHeap; 62 mPss = pss; 63 mAsJavaHeap = asJavaHeap; 64 mAsNativeHeap = asNativeHeap; 65 mAsCode = asCode; 66 mAsStack = asStack; 67 mAsGraphics = asGraphics; 68 mAsOther = asOther; 69 mAsSystem = asSystem; 70 mAsOverallPss = asOverallPss; 71 mInForeground = inForeground; 72 } 73 MemHealthRecord( String procName, long timeMs, long dalvikHeap, long nativeHeap, long pss, boolean inForeground)74 public MemHealthRecord( 75 String procName, long timeMs, long dalvikHeap, 76 long nativeHeap, long pss, boolean inForeground) { 77 this(procName, timeMs, dalvikHeap, nativeHeap, pss, 0, 0, 0, 0, 0, 0, 0, 0, inForeground); 78 } 79 80 /* Static methods */ 81 get( Instrumentation instr, List<String> procNames, long timeMs, List<String> foregroundProcs)82 static List<MemHealthRecord> get( 83 Instrumentation instr, 84 List<String> procNames, 85 long timeMs, 86 List<String> foregroundProcs) throws IOException { 87 88 List<MemHealthRecord> records = new ArrayList<>(); 89 90 for (String procName : procNames) { 91 String meminfo = getMeminfoOutput(instr, procName); 92 int nativeHeap = parseMeminfoLine(meminfo, "Native Heap\\s+\\d+\\s+(\\d+)"); 93 int dalvikHeap = parseMeminfoLine(meminfo, "Dalvik Heap\\s+\\d+\\s+(\\d+)"); 94 int pss = parseMeminfoLine(meminfo, "TOTAL\\s+(\\d+)"); 95 96 int asJavaHeap = parseMeminfoLine(meminfo, "Java Heap:\\s+(\\d+)"); 97 int asNativeHeap = parseMeminfoLine(meminfo, "Native Heap:\\s+(\\d+)"); 98 int asCode = parseMeminfoLine(meminfo, "Code:\\s+(\\d+)"); 99 int asStack = parseMeminfoLine(meminfo, "Stack:\\s+(\\d+)"); 100 int asGraphics = parseMeminfoLine(meminfo, "Graphics:\\s+(\\d+)"); 101 int asOther = parseMeminfoLine(meminfo, "Private Other:\\s+(\\d+)"); 102 int asSystem = parseMeminfoLine(meminfo, "System:\\s+(\\d+)"); 103 int asOverallPss = parseMeminfoLine(meminfo, "TOTAL:\\s+(\\d+)"); 104 105 if (nativeHeap < 0 || dalvikHeap < 0 || pss < 0) { 106 continue; 107 } 108 109 records.add(new MemHealthRecord( 110 procName, timeMs, dalvikHeap, nativeHeap, pss, asJavaHeap, 111 asNativeHeap, asCode, asStack, asGraphics, asOther, asSystem, 112 asOverallPss, foregroundProcs.contains(procName))); 113 } 114 115 return records; 116 } 117 saveVerbose(Collection<MemHealthRecord> allRecords, String fileName)118 static void saveVerbose(Collection<MemHealthRecord> allRecords, String fileName) 119 throws IOException { 120 PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(fileName, true))); 121 122 Map<String, List<MemHealthRecord>> fgRecords = getRecordMap(allRecords, true); 123 Map<String, List<MemHealthRecord>> bgRecords = getRecordMap(allRecords, false); 124 125 out.println("Foreground"); 126 127 for (Map.Entry<String, List<MemHealthRecord>> entry : fgRecords.entrySet()) { 128 String procName = entry.getKey(); 129 List<MemHealthRecord> records = entry.getValue(); 130 131 List<Long> nativeHeap = getForegroundNativeHeap(records); 132 List<Long> dalvikHeap = getForegroundDalvikHeap(records); 133 List<Long> pss = getForegroundPss(records); 134 List<Long> asJavaHeap = getForegroundSummaryJavaHeap(records); 135 List<Long> asNativeHeap = getForegroundSummaryNativeHeap(records); 136 List<Long> asCode = getForegroundSummaryCode(records); 137 List<Long> asStack = getForegroundSummaryStack(records); 138 List<Long> asGraphics = getForegroundSummaryGraphics(records); 139 List<Long> asOther = getForegroundSummaryOther(records); 140 List<Long> asSystem = getForegroundSummarySystem(records); 141 List<Long> asOverallPss = getForegroundSummaryOverallPss(records); 142 143 // nativeHeap, dalvikHeap, and pss all have the same size, just use one 144 if (nativeHeap.isEmpty()) { 145 continue; 146 } 147 148 out.println(procName); 149 out.printf("Average Native Heap: %d\n", getAverage(nativeHeap)); 150 out.printf("Average Dalvik Heap: %d\n", getAverage(dalvikHeap)); 151 out.printf("Average PSS: %d\n", getAverage(pss)); 152 out.printf("Peak Native Heap: %d\n", getMax(nativeHeap)); 153 out.printf("Peak Dalvik Heap: %d\n", getMax(dalvikHeap)); 154 out.printf("Peak PSS: %d\n", getMax(pss)); 155 out.printf("Count %d\n", nativeHeap.size()); 156 157 out.printf("Average Summary Java Heap: %d\n", getAverage(asJavaHeap)); 158 out.printf("Average Summary Native Heap: %d\n", getAverage(asNativeHeap)); 159 out.printf("Average Summary Code: %d\n", getAverage(asCode)); 160 out.printf("Average Summary Stack: %d\n", getAverage(asStack)); 161 out.printf("Average Summary Graphics: %d\n", getAverage(asGraphics)); 162 out.printf("Average Summary Other: %d\n", getAverage(asOther)); 163 out.printf("Average Summary System: %d\n", getAverage(asSystem)); 164 out.printf("Average Summary Overall Pss: %d\n", getAverage(asOverallPss)); 165 } 166 167 out.println("Background"); 168 for (Map.Entry<String, List<MemHealthRecord>> entry : bgRecords.entrySet()) { 169 String procName = entry.getKey(); 170 List<MemHealthRecord> records = entry.getValue(); 171 172 List<Long> nativeHeap = getBackgroundNativeHeap(records); 173 List<Long> dalvikHeap = getBackgroundDalvikHeap(records); 174 List<Long> pss = getBackgroundPss(records); 175 List<Long> asJavaHeap = getBackgroundSummaryJavaHeap(records); 176 List<Long> asNativeHeap = getBackgroundSummaryNativeHeap(records); 177 List<Long> asCode = getBackgroundSummaryCode(records); 178 List<Long> asStack = getBackgroundSummaryStack(records); 179 List<Long> asGraphics = getBackgroundSummaryGraphics(records); 180 List<Long> asOther = getBackgroundSummaryOther(records); 181 List<Long> asSystem = getBackgroundSummarySystem(records); 182 List<Long> asOverallPss = getBackgroundSummaryOverallPss(records); 183 184 // nativeHeap, dalvikHeap, and pss all have the same size, just use one 185 if (nativeHeap.isEmpty()) { 186 continue; 187 } 188 189 out.println(procName); 190 out.printf("Average Native Heap: %d\n", getAverage(nativeHeap)); 191 out.printf("Average Dalvik Heap: %d\n", getAverage(dalvikHeap)); 192 out.printf("Average PSS: %d\n", getAverage(pss)); 193 out.printf("Peak Native Heap: %d\n", getMax(nativeHeap)); 194 out.printf("Peak Dalvik Heap: %d\n", getMax(dalvikHeap)); 195 out.printf("Peak PSS: %d\n", getMax(pss)); 196 out.printf("Count %d\n", nativeHeap.size()); 197 198 out.printf("Average Summary Java Heap: %d\n", getAverage(asJavaHeap)); 199 out.printf("Average Summary Native Heap: %d\n", getAverage(asNativeHeap)); 200 out.printf("Average Summary Code: %d\n", getAverage(asCode)); 201 out.printf("Average Summary Stack: %d\n", getAverage(asStack)); 202 out.printf("Average Summary Graphics: %d\n", getAverage(asGraphics)); 203 out.printf("Average Summary Other: %d\n", getAverage(asOther)); 204 out.printf("Average Summary System: %d\n", getAverage(asSystem)); 205 out.printf("Average Summary Overall Pss: %d\n", getAverage(asOverallPss)); 206 } 207 208 out.close(); 209 } 210 211 /** 212 * NOTE (rsloan): I've meaningfully changed this format because the previous iteration was a 213 * horrific mix of CSV and not-CSV 214 */ saveCsv(Collection<MemHealthRecord> allRecords, String fileName)215 static void saveCsv(Collection<MemHealthRecord> allRecords, String fileName) throws IOException{ 216 PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(fileName, true))); 217 218 out.printf("name,time,native_heap,dalvik_heap,pss,context\n"); 219 for (MemHealthRecord record : allRecords) { 220 out.printf("%s,%d,%d,%d,%s\n", 221 record.mProcName, record.mTimeMs, record.mNativeHeap, 222 record.mDalvikHeap, record.mInForeground ? "foreground" : "background"); 223 } 224 225 out.close(); 226 } 227 228 /* Getters defined on a Collection<MemHealthRecord> */ 229 getForegroundDalvikHeap(Collection<MemHealthRecord> samples)230 public static List<Long> getForegroundDalvikHeap(Collection<MemHealthRecord> samples) { 231 List<Long> ret = new ArrayList<>(samples.size()); 232 for (MemHealthRecord sample : samples) { 233 if (sample.mInForeground) { 234 ret.add(sample.mDalvikHeap); 235 } 236 } 237 return ret; 238 } 239 getBackgroundDalvikHeap(Collection<MemHealthRecord> samples)240 public static List<Long> getBackgroundDalvikHeap(Collection<MemHealthRecord> samples) { 241 List<Long> ret = new ArrayList<>(samples.size()); 242 for (MemHealthRecord sample : samples) { 243 if (!sample.mInForeground) { 244 ret.add(sample.mDalvikHeap); 245 } 246 } 247 return ret; 248 } 249 getForegroundNativeHeap(Collection<MemHealthRecord> samples)250 public static List<Long> getForegroundNativeHeap(Collection<MemHealthRecord> samples) { 251 List<Long> ret = new ArrayList<>(samples.size()); 252 for (MemHealthRecord sample : samples) { 253 if (sample.mInForeground) { 254 ret.add(sample.mNativeHeap); 255 } 256 } 257 return ret; 258 } 259 getBackgroundNativeHeap(Collection<MemHealthRecord> samples)260 public static List<Long> getBackgroundNativeHeap(Collection<MemHealthRecord> samples) { 261 List<Long> ret = new ArrayList<>(samples.size()); 262 for (MemHealthRecord sample : samples) { 263 if (!sample.mInForeground) { 264 ret.add(sample.mNativeHeap); 265 } 266 } 267 return ret; 268 } 269 getForegroundPss(Collection<MemHealthRecord> samples)270 public static List<Long> getForegroundPss(Collection<MemHealthRecord> samples) { 271 List<Long> ret = new ArrayList<>(samples.size()); 272 for (MemHealthRecord sample : samples) { 273 if (sample.mInForeground) { 274 ret.add(sample.mPss); 275 } 276 } 277 return ret; 278 } 279 getBackgroundPss(Collection<MemHealthRecord> samples)280 public static List<Long> getBackgroundPss(Collection<MemHealthRecord> samples) { 281 List<Long> ret = new ArrayList<>(samples.size()); 282 for (MemHealthRecord sample : samples) { 283 if (!sample.mInForeground) { 284 ret.add(sample.mPss); 285 } 286 } 287 return ret; 288 } 289 getForegroundSummaryJavaHeap(Collection<MemHealthRecord> samples)290 public static List<Long> getForegroundSummaryJavaHeap(Collection<MemHealthRecord> samples) { 291 List<Long> ret = new ArrayList<>(samples.size()); 292 for (MemHealthRecord sample : samples) { 293 if (sample.mInForeground) { 294 ret.add(sample.mAsJavaHeap); 295 } 296 } 297 return ret; 298 } 299 getBackgroundSummaryJavaHeap(Collection<MemHealthRecord> samples)300 public static List<Long> getBackgroundSummaryJavaHeap(Collection<MemHealthRecord> samples) { 301 List<Long> ret = new ArrayList<>(samples.size()); 302 for (MemHealthRecord sample : samples) { 303 if (!sample.mInForeground) { 304 ret.add(sample.mAsJavaHeap); 305 } 306 } 307 return ret; 308 } 309 getForegroundSummaryNativeHeap( Collection<MemHealthRecord> samples)310 public static List<Long> getForegroundSummaryNativeHeap( 311 Collection<MemHealthRecord> samples) { 312 List<Long> ret = new ArrayList<>(samples.size()); 313 for (MemHealthRecord sample : samples) { 314 if (sample.mInForeground) { 315 ret.add(sample.mAsNativeHeap); 316 } 317 } 318 return ret; 319 } 320 getBackgroundSummaryNativeHeap( Collection<MemHealthRecord> samples)321 public static List<Long> getBackgroundSummaryNativeHeap( 322 Collection<MemHealthRecord> samples) { 323 List<Long> ret = new ArrayList<>(samples.size()); 324 for (MemHealthRecord sample : samples) { 325 if (!sample.mInForeground) { 326 ret.add(sample.mAsNativeHeap); 327 } 328 } 329 return ret; 330 } 331 getForegroundSummaryCode(Collection<MemHealthRecord> samples)332 public static List<Long> getForegroundSummaryCode(Collection<MemHealthRecord> samples) { 333 List<Long> ret = new ArrayList<>(samples.size()); 334 for (MemHealthRecord sample : samples) { 335 if (sample.mInForeground) { 336 ret.add(sample.mAsCode); 337 } 338 } 339 return ret; 340 } 341 getBackgroundSummaryCode(Collection<MemHealthRecord> samples)342 public static List<Long> getBackgroundSummaryCode(Collection<MemHealthRecord> samples) { 343 List<Long> ret = new ArrayList<>(samples.size()); 344 for (MemHealthRecord sample : samples) { 345 if (!sample.mInForeground) { 346 ret.add(sample.mAsCode); 347 } 348 } 349 return ret; 350 } 351 getForegroundSummaryStack(Collection<MemHealthRecord> samples)352 public static List<Long> getForegroundSummaryStack(Collection<MemHealthRecord> samples) { 353 List<Long> ret = new ArrayList<>(samples.size()); 354 for (MemHealthRecord sample : samples) { 355 if (sample.mInForeground) { 356 ret.add(sample.mAsStack); 357 } 358 } 359 return ret; 360 } 361 getBackgroundSummaryStack(Collection<MemHealthRecord> samples)362 public static List<Long> getBackgroundSummaryStack(Collection<MemHealthRecord> samples) { 363 List<Long> ret = new ArrayList<>(samples.size()); 364 for (MemHealthRecord sample : samples) { 365 if (!sample.mInForeground) { 366 ret.add(sample.mAsStack); 367 } 368 } 369 return ret; 370 } 371 getForegroundSummaryGraphics(Collection<MemHealthRecord> samples)372 public static List<Long> getForegroundSummaryGraphics(Collection<MemHealthRecord> samples) { 373 List<Long> ret = new ArrayList<>(samples.size()); 374 for (MemHealthRecord sample : samples) { 375 if (sample.mInForeground) { 376 ret.add(sample.mAsGraphics); 377 } 378 } 379 return ret; 380 } 381 getBackgroundSummaryGraphics(Collection<MemHealthRecord> samples)382 public static List<Long> getBackgroundSummaryGraphics(Collection<MemHealthRecord> samples) { 383 List<Long> ret = new ArrayList<>(samples.size()); 384 for (MemHealthRecord sample : samples) { 385 if (!sample.mInForeground) { 386 ret.add(sample.mAsGraphics); 387 } 388 } 389 return ret; 390 } 391 getForegroundSummaryOther(Collection<MemHealthRecord> samples)392 public static List<Long> getForegroundSummaryOther(Collection<MemHealthRecord> samples) { 393 List<Long> ret = new ArrayList<>(samples.size()); 394 for (MemHealthRecord sample : samples) { 395 if (sample.mInForeground) { 396 ret.add(sample.mAsOther); 397 } 398 } 399 return ret; 400 } 401 getBackgroundSummaryOther(Collection<MemHealthRecord> samples)402 public static List<Long> getBackgroundSummaryOther(Collection<MemHealthRecord> samples) { 403 List<Long> ret = new ArrayList<>(samples.size()); 404 for (MemHealthRecord sample : samples) { 405 if (!sample.mInForeground) { 406 ret.add(sample.mAsOther); 407 } 408 } 409 return ret; 410 } 411 getForegroundSummarySystem(Collection<MemHealthRecord> samples)412 public static List<Long> getForegroundSummarySystem(Collection<MemHealthRecord> samples) { 413 List<Long> ret = new ArrayList<>(samples.size()); 414 for (MemHealthRecord sample : samples) { 415 if (sample.mInForeground) { 416 ret.add(sample.mAsSystem); 417 } 418 } 419 return ret; 420 } 421 getBackgroundSummarySystem(Collection<MemHealthRecord> samples)422 public static List<Long> getBackgroundSummarySystem(Collection<MemHealthRecord> samples) { 423 List<Long> ret = new ArrayList<>(samples.size()); 424 for (MemHealthRecord sample : samples) { 425 if (!sample.mInForeground) { 426 ret.add(sample.mAsSystem); 427 } 428 } 429 return ret; 430 } 431 getForegroundSummaryOverallPss( Collection<MemHealthRecord> samples)432 public static List<Long> getForegroundSummaryOverallPss( 433 Collection<MemHealthRecord> samples) { 434 List<Long> ret = new ArrayList<>(samples.size()); 435 for (MemHealthRecord sample : samples) { 436 if (sample.mInForeground) { 437 ret.add(sample.mAsOverallPss); 438 } 439 } 440 return ret; 441 } 442 getBackgroundSummaryOverallPss( Collection<MemHealthRecord> samples)443 public static List<Long> getBackgroundSummaryOverallPss( 444 Collection<MemHealthRecord> samples) { 445 List<Long> ret = new ArrayList<>(samples.size()); 446 for (MemHealthRecord sample : samples) { 447 if (!sample.mInForeground) { 448 ret.add(sample.mAsOverallPss); 449 } 450 } 451 return ret; 452 } 453 454 /* Utility Methods */ 455 getMax(Collection<Long> samples)456 private static Long getMax(Collection<Long> samples) { 457 Long max = null; 458 for (Long sample : samples) { 459 if (max == null || sample > max) { 460 max = sample; 461 } 462 } 463 return max; 464 } 465 getAverage(Collection<Long> samples)466 private static Long getAverage(Collection<Long> samples) { 467 if (samples.size() == 0) { 468 return null; 469 } 470 471 double sum = 0; 472 for (Long sample : samples) { 473 sum += sample; 474 } 475 return (long) (sum / samples.size()); 476 } 477 parseMeminfoLine(String meminfo, String pattern)478 private static int parseMeminfoLine(String meminfo, String pattern) 479 { 480 Pattern p = Pattern.compile(pattern); 481 Matcher m = p.matcher(meminfo); 482 if (m.find()) { 483 return Integer.parseInt(m.group(1)); 484 } else { 485 return -1; 486 } 487 } 488 getMeminfoOutput(Instrumentation instr, String processName)489 public static String getMeminfoOutput(Instrumentation instr, String processName) 490 throws IOException { 491 return getProcessOutput(instr, "dumpsys meminfo " + processName); 492 } 493 getProcessOutput(Instrumentation instr, String command)494 public static String getProcessOutput(Instrumentation instr, String command) 495 throws IOException { 496 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 497 FilesystemUtil.saveProcessOutput(instr, command, baos); 498 baos.close(); 499 return baos.toString(); 500 } 501 getRecordMap( Collection<MemHealthRecord> allRecords, boolean inForeground)502 private static Map<String, List<MemHealthRecord>> getRecordMap( 503 Collection<MemHealthRecord> allRecords, 504 boolean inForeground) { 505 506 Map<String, List<MemHealthRecord>> records = new HashMap<>(); 507 508 for (MemHealthRecord record : allRecords) { 509 if (record.mInForeground == inForeground) { 510 if (!records.containsKey(record.mProcName)) { 511 records.put(record.mProcName, new ArrayList<MemHealthRecord>()); 512 } 513 514 records.get(record.mProcName).add(record); 515 } 516 } 517 518 return records; 519 } 520 521 } 522