1 /*
2  * Copyright (C) 2011 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 com.android.media.tests;
18 
19 import com.android.ddmlib.IDevice;
20 import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner;
21 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
22 import com.android.tradefed.config.Option;
23 import com.android.tradefed.device.DeviceNotAvailableException;
24 import com.android.tradefed.device.ITestDevice;
25 import com.android.tradefed.log.LogUtil.CLog;
26 import com.android.tradefed.result.BugreportCollector;
27 import com.android.tradefed.result.BugreportCollector.Freq;
28 import com.android.tradefed.result.BugreportCollector.Noun;
29 import com.android.tradefed.result.BugreportCollector.Relation;
30 import com.android.tradefed.result.FileInputStreamSource;
31 import com.android.tradefed.result.ITestInvocationListener;
32 import com.android.tradefed.result.InputStreamSource;
33 import com.android.tradefed.result.LogDataType;
34 import com.android.tradefed.testtype.IDeviceTest;
35 import com.android.tradefed.testtype.IRemoteTest;
36 import com.android.tradefed.util.FileUtil;
37 import com.android.tradefed.util.StreamUtil;
38 import com.android.tradefed.util.proto.TfMetricProtoUtil;
39 
40 import org.junit.Assert;
41 
42 import java.io.File;
43 import java.io.FileInputStream;
44 import java.io.IOException;
45 import java.io.InputStream;
46 import java.util.Arrays;
47 import java.util.Collection;
48 import java.util.HashMap;
49 import java.util.LinkedList;
50 import java.util.List;
51 import java.util.ListIterator;
52 import java.util.Map;
53 import java.util.concurrent.TimeUnit;
54 import java.util.regex.Matcher;
55 import java.util.regex.Pattern;
56 
57 /**
58  * Runs the Media memory test. This test will do various media actions ( ie.
59  * playback, recording and etc.) then capture the snapshot of mediaserver memory
60  * usage. The test summary is save to /sdcard/mediaMemOutput.txt
61  * <p/>
62  * Note that this test will not run properly unless /sdcard is mounted and
63  * writable.
64  */
65 public class MediaMemoryTest implements IDeviceTest, IRemoteTest {
66 
67     ITestDevice mTestDevice = null;
68 
69     private static final String METRICS_RUN_NAME = "MediaMemoryLeak";
70 
71     // Constants for running the tests
72     private static final String TEST_CLASS_NAME =
73             "com.android.mediaframeworktest.performance.MediaPlayerPerformance";
74     private static final String TEST_PACKAGE_NAME = "com.android.mediaframeworktest";
75     private static final String TEST_RUNNER_NAME = ".MediaFrameworkPerfTestRunner";
76 
77     private final String mOutputPaths[] = {"mediaMemOutput.txt","mediaProcmemOutput.txt"};
78 
79     //Max test timeout - 4 hrs
80     private static final int MAX_TEST_TIMEOUT = 4 * 60 * 60 * 1000;
81 
82     public Map<String, String> mPatternMap = new HashMap<>();
83     private static final Pattern TOTAL_MEM_DIFF_PATTERN =
84             Pattern.compile("^The total diff = (\\d+)");
85 
86     @Option(name = "getHeapDump", description = "Collect the heap ")
87     private boolean mGetHeapDump = false;
88 
89     @Option(name = "getProcMem", description = "Collect the procmem info ")
90     private boolean mGetProcMem = false;
91 
92     @Option(name = "testName", description = "Test name to run. May be repeated.")
93     private Collection<String> mTests = new LinkedList<>();
94 
MediaMemoryTest()95     public MediaMemoryTest() {
96         mPatternMap.put("testCameraPreviewMemoryUsage", "CameraPreview");
97         mPatternMap.put("testRecordAudioOnlyMemoryUsage", "AudioRecord");
98         mPatternMap.put("testH263VideoPlaybackMemoryUsage", "H263Playback");
99         mPatternMap.put("testRecordVideoAudioMemoryUsage", "H263RecordVideoAudio");
100         mPatternMap.put("testH263RecordVideoOnlyMemoryUsage", "H263RecordVideoOnly");
101         mPatternMap.put("testH264VideoPlaybackMemoryUsage", "H264Playback");
102         mPatternMap.put("testMpeg4RecordVideoOnlyMemoryUsage", "MPEG4RecordVideoOnly");
103     }
104 
105 
106     @Override
run(ITestInvocationListener listener)107     public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
108         Assert.assertNotNull(mTestDevice);
109 
110         IRemoteAndroidTestRunner runner = new RemoteAndroidTestRunner(TEST_PACKAGE_NAME,
111                 TEST_RUNNER_NAME, mTestDevice.getIDevice());
112         runner.setClassName(TEST_CLASS_NAME);
113         runner.setMaxTimeToOutputResponse(MAX_TEST_TIMEOUT, TimeUnit.MILLISECONDS);
114         if (mGetHeapDump) {
115             runner.addInstrumentationArg("get_heap_dump", "true");
116         }
117         if (mGetProcMem) {
118             runner.addInstrumentationArg("get_procmem", "true");
119         }
120 
121         BugreportCollector bugListener = new BugreportCollector(listener,
122                 mTestDevice);
123         bugListener.addPredicate(new BugreportCollector.Predicate(
124                 Relation.AFTER, Freq.EACH, Noun.TESTRUN));
125 
126         if (mTests.size() > 0) {
127             for (String testName : mTests) {
128                 runner.setMethodName(TEST_CLASS_NAME, testName);
129                 mTestDevice.runInstrumentationTests(runner, bugListener);
130             }
131         } else {
132             mTestDevice.runInstrumentationTests(runner, bugListener);
133         }
134         logOutputFiles(listener);
135         cleanResultFile();
136     }
137 
138     /**
139      * Clean up the test result file from test run
140      */
cleanResultFile()141     private void cleanResultFile() throws DeviceNotAvailableException {
142         String extStore = mTestDevice.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
143         for(String outputPath : mOutputPaths){
144             mTestDevice.executeShellCommand(String.format("rm %s/%s", extStore, outputPath));
145         }
146         if (mGetHeapDump) {
147             mTestDevice.executeShellCommand(String.format("rm %s/%s", extStore, "*.dump"));
148         }
149     }
150 
uploadHeapDumpFiles(ITestInvocationListener listener)151     private void uploadHeapDumpFiles(ITestInvocationListener listener)
152             throws DeviceNotAvailableException {
153         // Pull and upload the heap dump output files.
154         InputStreamSource outputSource = null;
155         File outputFile = null;
156 
157         String extStore = mTestDevice.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
158 
159         String out = mTestDevice.executeShellCommand(String.format("ls %s/%s",
160                 extStore, "*.dump"));
161         String heapOutputFiles[] = out.split("\n");
162 
163         for (String heapFile : heapOutputFiles) {
164             try {
165                 outputFile = mTestDevice.pullFile(heapFile.trim());
166                 if (outputFile == null) {
167                     continue;
168                 }
169                 outputSource = new FileInputStreamSource(outputFile);
170                 listener.testLog(heapFile, LogDataType.TEXT, outputSource);
171             } finally {
172                 FileUtil.deleteFile(outputFile);
173                 StreamUtil.cancel(outputSource);
174             }
175         }
176     }
177 
178     /**
179      * Pull the output files from the device, add it to the logs, and also parse
180      * out the relevant test metrics and report them.
181      */
logOutputFiles(ITestInvocationListener listener)182     private void logOutputFiles(ITestInvocationListener listener)
183             throws DeviceNotAvailableException {
184         File outputFile = null;
185         InputStreamSource outputSource = null;
186 
187         if (mGetHeapDump) {
188             // Upload all the heap dump files.
189             uploadHeapDumpFiles(listener);
190         }
191         for(String outputPath : mOutputPaths){
192             try {
193                 outputFile = mTestDevice.pullFileFromExternal(outputPath);
194 
195                 if (outputFile == null) {
196                     return;
197                 }
198 
199                 // Upload a verbatim copy of the output file
200                 CLog.d("Sending %d byte file %s into the logosphere!",
201                         outputFile.length(), outputFile);
202                 outputSource = new FileInputStreamSource(outputFile);
203                 listener.testLog(outputPath, LogDataType.TEXT, outputSource);
204 
205                 // Parse the output file to upload aggregated metrics
206                 parseOutputFile(new FileInputStream(outputFile), listener);
207             } catch (IOException e) {
208                 CLog.e("IOException while reading or parsing output file: %s",
209                        e.getMessage());
210             } finally {
211                 FileUtil.deleteFile(outputFile);
212                 StreamUtil.cancel(outputSource);
213             }
214         }
215     }
216 
217     /**
218      * Parse the relevant metrics from the Instrumentation test output file
219      */
parseOutputFile(InputStream dataStream, ITestInvocationListener listener)220     private void parseOutputFile(InputStream dataStream,
221             ITestInvocationListener listener) {
222 
223         Map<String, String> runMetrics = new HashMap<>();
224 
225         // try to parse it
226         String contents;
227         try {
228             contents = StreamUtil.getStringFromStream(dataStream);
229         } catch (IOException e) {
230             CLog.e("Got IOException during test processing: %s",
231                    e.getMessage());
232             return;
233         }
234 
235         List<String> lines = Arrays.asList(contents.split("\n"));
236         ListIterator<String> lineIter = lines.listIterator();
237         String line;
238         while (lineIter.hasNext()) {
239             line = lineIter.next();
240             if (mPatternMap.containsKey(line)) {
241 
242                 String key = mPatternMap.get(line);
243                 // Look for the total diff
244                 while (lineIter.hasNext()) {
245                     line = lineIter.next();
246                     Matcher m = TOTAL_MEM_DIFF_PATTERN.matcher(line);
247                     if (m.matches()) {
248                         int result = Integer.parseInt(m.group(1));
249                         runMetrics.put(key, Integer.toString(result));
250                         break;
251                     }
252                 }
253             } else {
254                 CLog.e("Got unmatched line: %s", line);
255                 continue;
256             }
257         }
258         reportMetrics(listener, runMetrics);
259     }
260 
261     /**
262      * Report run metrics by creating an empty test run to stick them in
263      * <p />
264      * Exposed for unit testing
265      */
reportMetrics(ITestInvocationListener listener, Map<String, String> metrics)266     void reportMetrics(ITestInvocationListener listener, Map<String, String> metrics) {
267         CLog.d("About to report metrics: %s", metrics);
268         listener.testRunStarted(METRICS_RUN_NAME, 0);
269         listener.testRunEnded(0, TfMetricProtoUtil.upgradeConvert(metrics));
270     }
271 
272     @Override
setDevice(ITestDevice device)273     public void setDevice(ITestDevice device) {
274         mTestDevice = device;
275     }
276 
277     @Override
getDevice()278     public ITestDevice getDevice() {
279         return mTestDevice;
280     }
281 }
282