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