• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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