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 android.app.Instrumentation;
20 import android.os.Environment;
21 import android.os.ParcelFileDescriptor;
22 import android.os.SystemClock;
23 import android.util.Log;
24 
25 import java.io.ByteArrayOutputStream;
26 import java.io.File;
27 import java.io.FileNotFoundException;
28 import java.io.FileOutputStream;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.io.OutputStream;
32 import java.text.SimpleDateFormat;
33 import java.util.Date;
34 import java.util.Locale;
35 
36 public class DataCollector {
37     private static final String TAG = "AuptDataCollector";
38     private long mBugreportInterval, mMeminfoInterval, mCpuinfoInterval, mFragmentationInterval,
39             mIonHeapInterval, mPageTypeInfoInterval, mTraceInterval;
40     private File mResultsDirectory;
41 
42     private Thread mLoggerThread;
43     private Logger mLogger;
44     private Instrumentation mInstrumentation;
45 
DataCollector(long bugreportInterval, long meminfoInterval, long cpuinfoInterval, long fragmentationInterval, long ionHeapInterval, long pagetypeinfoInterval, long traceInterval, File outputLocation, Instrumentation intrumentation)46     public DataCollector(long bugreportInterval, long meminfoInterval, long cpuinfoInterval,
47             long fragmentationInterval, long ionHeapInterval, long pagetypeinfoInterval,
48             long traceInterval, File outputLocation, Instrumentation intrumentation) {
49         mBugreportInterval = bugreportInterval;
50         mMeminfoInterval = meminfoInterval;
51         mCpuinfoInterval = cpuinfoInterval;
52         mFragmentationInterval = fragmentationInterval;
53         mIonHeapInterval = ionHeapInterval;
54         mPageTypeInfoInterval = pagetypeinfoInterval;
55         mResultsDirectory = outputLocation;
56         mTraceInterval = traceInterval;
57         mInstrumentation = intrumentation;
58     }
59 
start()60     public void start() {
61         mLogger = new Logger();
62         mLoggerThread = new Thread(mLogger);
63         mLoggerThread.start();
64     }
65 
stop()66     public void stop() {
67         mLogger.stop();
68         try {
69             mLoggerThread.join();
70         } catch (InterruptedException e) {
71             // ignore
72         }
73     }
74 
75     private class Logger implements Runnable {
76         private final long mIntervals[] = {
77                 mBugreportInterval, mMeminfoInterval, mCpuinfoInterval, mFragmentationInterval,
78                 mIonHeapInterval, mPageTypeInfoInterval, mTraceInterval
79         };
80         private final LogGenerator mLoggers[] = {
81                 new BugreportGenerator(), new CompactMemInfoGenerator(), new CpuInfoGenerator(),
82                 new FragmentationGenerator(), new IonHeapGenerator(), new PageTypeInfoGenerator(),
83                 new TraceGenerator()
84         };
85 
86         private final long mLastUpdate[] = new long[mLoggers.length];
87         private final long mSleepInterval;
88 
89         private boolean mStopped = false;
90 
Logger()91         public Logger() {
92             for (int i = 0; i < mIntervals.length; i++) {
93                 if (mIntervals[i] > 0) {
94                     try {
95                         mLoggers[i].createLog();
96                     } catch (InterruptedException e) {
97                         // ignore
98                     }
99                     mLastUpdate[i] = SystemClock.uptimeMillis();
100                 }
101             }
102 
103             mSleepInterval = gcd(mIntervals);
104         }
105 
stop()106         public void stop() {
107             synchronized(this) {
108                 mStopped = true;
109                 notifyAll();
110             }
111         }
112 
gcd(long values[])113         private long gcd(long values[]) {
114             if (values.length < 2)
115                 return 0;
116 
117             long gcdSoFar = values[0];
118             for (int i = 1; i < values.length; i++) {
119                 gcdSoFar = gcd(gcdSoFar, values[i]);
120             }
121 
122             return gcdSoFar;
123         }
124 
gcd(long a, long b)125         private long gcd(long a, long b) {
126             if (a == 0)
127                 return b;
128             if (b == 0)
129                 return a;
130             if (a > b)
131                 return gcd(b, a % b);
132             else
133                 return gcd(a, b % a);
134         }
135 
136         @Override
run()137         public void run() {
138             if (mSleepInterval <= 0)
139                 return;
140 
141             synchronized(this) {
142                 while (!mStopped) {
143                     try {
144                         for (int i = 0; i < mIntervals.length; i++) {
145                             if (mIntervals[i] > 0 &&
146                                     SystemClock.uptimeMillis() - mLastUpdate[i] > mIntervals[i]) {
147                                 mLoggers[i].createLog();
148                                 mLastUpdate[i] = SystemClock.uptimeMillis();
149                             }
150                         }
151                         wait(mSleepInterval);
152                     } catch (InterruptedException e) {
153                         // Ignore.
154                     }
155                 }
156             }
157         }
158     }
159 
160     private interface LogGenerator {
createLog()161         public void createLog() throws InterruptedException;
162     }
163 
164     private class CompactMemInfoGenerator implements LogGenerator {
165         @Override
createLog()166         public void createLog() throws InterruptedException {
167             try {
168                 saveCompactMeminfo(mResultsDirectory + "/compact-meminfo-%s.txt");
169             } catch (IOException ioe) {
170                 Log.w(TAG, "Error while saving dumpsys meminfo -c: " + ioe.getMessage());
171             }
172         }
173     }
174 
175     private class CpuInfoGenerator implements LogGenerator {
176         @Override
createLog()177         public void createLog() throws InterruptedException {
178             try {
179                 saveCpuinfo(mResultsDirectory + "/cpuinfo-%s.txt");
180             } catch (IOException ioe) {
181                 Log.w(TAG, "Error while saving dumpsys cpuinfo : " + ioe.getMessage());
182             }
183         }
184     }
185 
186     private class BugreportGenerator implements LogGenerator {
187         @Override
createLog()188         public void createLog() throws InterruptedException {
189             try {
190                 saveBugreport(mResultsDirectory + "/bugreport-%s.txt");
191             } catch (IOException e) {
192                 Log.w(TAG, String.format("Failed to take bugreport: %s", e.getMessage()));
193             }
194         }
195     }
196 
197     private class FragmentationGenerator implements LogGenerator {
198         @Override
createLog()199         public void createLog() throws InterruptedException {
200             try {
201                 saveFragmentation(mResultsDirectory + "/unusable-index-%s.txt");
202             } catch (IOException e) {
203                 Log.w(TAG, String.format("Failed to save buddyinfo: %s", e.getMessage()));
204             }
205         }
206     }
207 
208     private class IonHeapGenerator implements LogGenerator {
209         @Override
createLog()210         public void createLog() throws InterruptedException {
211             try {
212                 saveIonHeap("audio", mResultsDirectory + "/ion-audio-%s.txt");
213                 saveIonHeap("system", mResultsDirectory + "/ion-system-%s.txt");
214             } catch (IOException e) {
215                 Log.w(TAG, String.format("Failed to save ION heap: %s", e.getMessage()));
216             }
217         }
218     }
219 
220     private class PageTypeInfoGenerator implements LogGenerator {
221         @Override
createLog()222         public void createLog() throws InterruptedException {
223             try {
224                 savePageTypeInfo(mResultsDirectory + "/pagetypeinfo-%s.txt");
225             } catch (IOException e) {
226                 Log.w(TAG, String.format("Failed to save pagetypeinfo: %s", e.getMessage()));
227             }
228         }
229     }
230 
231     private class TraceGenerator implements LogGenerator {
232         @Override
createLog()233         public void createLog() throws InterruptedException {
234             try {
235                 saveTrace(mResultsDirectory + "/trace-%s.txt");
236             } catch (IOException e) {
237                 Log.w(TAG, String.format("Failed to save trace: %s", e.getMessage()));
238             }
239         }
240     }
241 
saveCompactMeminfo(String filename)242     public void saveCompactMeminfo(String filename)
243             throws FileNotFoundException, IOException, InterruptedException {
244         saveProcessOutput("dumpsys meminfo -c -S", filename);
245     }
246 
saveCpuinfo(String filename)247     public void saveCpuinfo(String filename)
248             throws FileNotFoundException, IOException, InterruptedException {
249         saveProcessOutput("dumpsys cpuinfo", filename);
250     }
251 
saveFragmentation(String filename)252     public void saveFragmentation(String filename)
253             throws FileNotFoundException, IOException, InterruptedException {
254         saveProcessOutput("cat /d/extfrag/unusable_index", filename);
255     }
256 
saveIonHeap(String type, String filename)257     public void saveIonHeap(String type, String filename)
258             throws FileNotFoundException, IOException, InterruptedException {
259         saveProcessOutput(String.format("cat /d/ion/heaps/%s", type), filename);
260     }
261 
savePageTypeInfo(String filename)262     public void savePageTypeInfo(String filename)
263             throws FileNotFoundException, IOException, InterruptedException {
264         saveProcessOutput("cat /proc/pagetypeinfo", filename);
265     }
266 
saveTrace(String filename)267     public void saveTrace(String filename)
268             throws FileNotFoundException, IOException, InterruptedException {
269         saveProcessOutput("cat /sys/kernel/debug/tracing/trace", filename);
270     }
271 
saveBugreport(String filename)272     public void saveBugreport(String filename)
273             throws IOException, InterruptedException {
274         ByteArrayOutputStream baos = new ByteArrayOutputStream();
275         // Spaces matter in the following command line. Make sure there are no spaces
276         // in the filename and around the '>' sign.
277         String cmdline = String.format("/system/bin/sh -c /system/bin/bugreport>%s",
278                 templateToFilename(filename));
279         saveProcessOutput(cmdline, baos);
280         baos.close();
281     }
282 
dumpMeminfo(String notes)283     public void dumpMeminfo(String notes) {
284         long epochSeconds = System.currentTimeMillis() / 1000;
285         File outputDir = new File(Environment.getExternalStorageDirectory(), "meminfo");
286         Log.i(TAG, outputDir.toString());
287         if (!outputDir.exists()) {
288             boolean yes  = outputDir.mkdirs();
289             Log.i(TAG, yes ? "created" : "not created");
290         }
291         File outputFile = new File(outputDir, String.format("%d.txt", epochSeconds));
292         Log.i(TAG, outputFile.toString());
293         FileOutputStream fos = null;
294 
295         try {
296             fos = new FileOutputStream(outputFile);
297             fos.write(String.format("notes: %s\n\n", notes).getBytes());
298 
299             saveProcessOutput("dumpsys meminfo -c", fos);
300             fos.close();
301         } catch (FileNotFoundException e) {
302             Log.e(TAG, "exception while dumping meminfo", e);
303         } catch (IOException e) {
304             Log.e(TAG, "exception while dumping meminfo", e);
305         }
306     }
307 
saveProcessOutput(String command, String filenameTemplate)308     private void saveProcessOutput(String command, String filenameTemplate)
309             throws IOException, FileNotFoundException {
310         String outFilename = templateToFilename(filenameTemplate);
311         File file = new File(outFilename);
312         Log.d(TAG, String.format("Saving command \"%s\" output into file %s",
313                 command, file.getAbsolutePath()));
314 
315         OutputStream out = new FileOutputStream(file);
316         saveProcessOutput(command, out);
317         out.close();
318     }
319 
templateToFilename(String filenameTemplate)320     private String templateToFilename(String filenameTemplate) {
321         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US);
322         return String.format(filenameTemplate, sdf.format(new Date()));
323     }
324 
saveProcessOutput(String command, OutputStream out)325     public void saveProcessOutput(String command, OutputStream out) throws IOException {
326         InputStream in = null;
327         try {
328             ParcelFileDescriptor pfd =
329                     mInstrumentation.getUiAutomation().executeShellCommand(command);
330             in = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
331             byte[] buffer = new byte[4096];  //4K buffer
332             int bytesRead = -1;
333             while (true) {
334                 bytesRead = in.read(buffer);
335                 if (bytesRead == -1) {
336                     break;
337                 }
338                 out.write(buffer, 0, bytesRead);
339             }
340         } finally {
341             if (in != null) {
342                 in.close();
343             }
344             if (out != null) {
345                 out.flush();
346             }
347         }
348     }
349 }
350