Home | History | Annotate | Download | only in aupt
      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 
     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 
     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 
     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 
    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      */
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    489     public static String getMeminfoOutput(Instrumentation instr, String processName)
    490             throws IOException {
    491         return getProcessOutput(instr, "dumpsys meminfo " + processName);
    492     }
    493 
    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 
    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