• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.traceur;
18 
19 import android.app.ActivityManager;
20 import android.content.Context;
21 import android.os.Build;
22 import android.os.FileUtils;
23 import android.text.format.DateUtils;
24 import android.util.Log;
25 
26 import java.io.BufferedReader;
27 import java.io.File;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.io.InputStreamReader;
31 import java.io.OutputStream;
32 import java.text.SimpleDateFormat;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.Collection;
36 import java.util.Collections;
37 import java.util.Date;
38 import java.util.List;
39 import java.util.Locale;
40 import java.util.Optional;
41 import java.util.Set;
42 import java.util.TreeMap;
43 import java.util.concurrent.ExecutorService;
44 import java.util.concurrent.Executors;
45 import java.util.concurrent.FutureTask;
46 import java.util.concurrent.TimeUnit;
47 import java.util.stream.Collectors;
48 
49 /**
50  * Utility functions for tracing.
51  */
52 public class TraceUtils {
53 
54     static final String TAG = "Traceur";
55 
56     public static final String TRACE_DIRECTORY = "/data/local/traces/";
57 
58     private static PerfettoUtils mTraceEngine = new PerfettoUtils();
59 
60     private static final Runtime RUNTIME = Runtime.getRuntime();
61 
62     // The number of files to keep when clearing old traces.
63     private static final int MIN_KEEP_COUNT = 0;
64 
65     // The age that old traces should be cleared at.
66     private static final long MIN_KEEP_AGE = 4 * DateUtils.WEEK_IN_MILLIS;
67 
68     public enum RecordingType {
69         UNKNOWN, TRACE, STACK_SAMPLES, HEAP_DUMP
70     }
71 
traceStart(Context context, TraceConfig config)72     public static boolean traceStart(Context context, TraceConfig config) {
73         return traceStart(
74             context,
75             config.getTags(),
76             config.getBufferSizeKb(),
77             config.getWinscope(),
78             config.getApps(),
79             config.getLongTrace(),
80             config.getAttachToBugreport(),
81             config.getMaxLongTraceSizeMb(),
82             config.getMaxLongTraceDurationMinutes()
83         );
84     }
85 
traceStart(Context context, Collection<String> tags, int bufferSizeKb, boolean winscope, boolean apps, boolean longTrace, boolean attachToBugreport, int maxLongTraceSizeMb, int maxLongTraceDurationMinutes)86     public static boolean traceStart(Context context, Collection<String> tags,
87             int bufferSizeKb, boolean winscope, boolean apps, boolean longTrace,
88             boolean attachToBugreport, int maxLongTraceSizeMb, int maxLongTraceDurationMinutes) {
89         if (!mTraceEngine.traceStart(tags, bufferSizeKb, winscope, apps, longTrace,
90                 attachToBugreport, maxLongTraceSizeMb, maxLongTraceDurationMinutes)) {
91             return false;
92         }
93         WinscopeUtils.traceStart(context, winscope);
94         return true;
95     }
96 
stackSampleStart(boolean attachToBugreport)97     public static boolean stackSampleStart(boolean attachToBugreport) {
98         return mTraceEngine.stackSampleStart(attachToBugreport);
99     }
100 
heapDumpStart(Collection<String> processes, boolean continuousDump, int dumpIntervalSeconds, boolean attachToBugreport)101     public static boolean heapDumpStart(Collection<String> processes, boolean continuousDump,
102             int dumpIntervalSeconds, boolean attachToBugreport) {
103         return mTraceEngine.heapDumpStart(processes, continuousDump, dumpIntervalSeconds,
104                 attachToBugreport);
105     }
106 
traceStop(Context context)107     public static void traceStop(Context context) {
108         mTraceEngine.traceStop();
109         WinscopeUtils.traceStop(context);
110     }
111 
traceDump(Context context, String outFilename)112     public static Optional<List<File>> traceDump(Context context, String outFilename) {
113         File outFile = TraceUtils.getOutputFile(outFilename);
114         if (!mTraceEngine.traceDump(outFile)) {
115             return Optional.empty();
116         }
117 
118         List<File> outFiles = new ArrayList();
119         outFiles.add(outFile);
120 
121         List<File> outLegacyWinscopeFiles = WinscopeUtils.traceDump(context, outFilename);
122         outFiles.addAll(outLegacyWinscopeFiles);
123 
124         return Optional.of(outFiles);
125     }
126 
isTracingOn()127     public static boolean isTracingOn() {
128         return mTraceEngine.isTracingOn();
129     }
130 
listCategories()131     public static TreeMap<String, String> listCategories() {
132         TreeMap<String, String> categories = PerfettoUtils.perfettoListCategories();
133         categories.put("sys_stats", "meminfo, psi, and vmstats");
134         categories.put("logs", "android logcat");
135         categories.put("cpu", "callstack samples");
136         return categories;
137     }
138 
clearSavedTraces()139     public static void clearSavedTraces() {
140         String cmd = "rm -f " + TRACE_DIRECTORY + "trace-*.*trace " +
141                 TRACE_DIRECTORY + "recovered-trace*.*trace " +
142                 TRACE_DIRECTORY + "stack-samples*.*trace " +
143                 TRACE_DIRECTORY + "heap-dump*.*trace";
144 
145         Log.v(TAG, "Clearing trace directory: " + cmd);
146         try {
147             Process rm = exec(cmd);
148 
149             if (rm.waitFor() != 0) {
150                 Log.e(TAG, "clearSavedTraces failed with: " + rm.exitValue());
151             }
152         } catch (Exception e) {
153             throw new RuntimeException(e);
154         }
155     }
156 
exec(String cmd)157     public static Process exec(String cmd) throws IOException {
158         return exec(cmd, null);
159     }
160 
exec(String cmd, String tmpdir)161     public static Process exec(String cmd, String tmpdir) throws IOException {
162         return exec(cmd, tmpdir, true);
163     }
164 
exec(String cmd, String tmpdir, boolean logOutput)165     public static Process exec(String cmd, String tmpdir, boolean logOutput) throws IOException {
166         String[] cmdarray = {"sh", "-c", cmd};
167         String[] envp = {"TMPDIR=" + tmpdir};
168         envp = tmpdir == null ? null : envp;
169 
170         Log.v(TAG, "exec: " + Arrays.toString(envp) + " " + Arrays.toString(cmdarray));
171 
172         Process process = RUNTIME.exec(cmdarray, envp);
173         new Logger("traceService:stderr", process.getErrorStream());
174         if (logOutput) {
175             new Logger("traceService:stdout", process.getInputStream());
176         }
177 
178         return process;
179     }
180 
execWithTimeout(String cmd, String tmpdir, long timeout)181     public static Process execWithTimeout(String cmd, String tmpdir, long timeout)
182             throws IOException {
183         return execWithTimeout(cmd, tmpdir, timeout, null);
184     }
185 
186     // Returns the Process if the command terminated on time and null if not.
execWithTimeout(String cmd, String tmpdir, long timeout, byte[] input)187     public static Process execWithTimeout(String cmd, String tmpdir, long timeout, byte[] input)
188             throws IOException {
189         Process process = exec(cmd, tmpdir, true);
190         try {
191             if (input != null) {
192                 OutputStream os = process.getOutputStream();
193                 os.write(input);
194                 os.flush();
195                 os.close();
196             }
197             if (!process.waitFor(timeout, TimeUnit.MILLISECONDS)) {
198                 Log.e(TAG, "Command '" + cmd + "' has timed out after " + timeout + " ms.");
199                 process.destroyForcibly();
200                 // Return null to signal a timeout and that the Process was destroyed.
201                 return null;
202             }
203         } catch (Exception e) {
204             throw new RuntimeException(e);
205         }
206         return process;
207     }
208 
getOutputFilename(RecordingType type)209     public static String getOutputFilename(RecordingType type) {
210         String prefix;
211         switch (type) {
212             case TRACE:
213                 prefix = "trace";
214                 break;
215             case STACK_SAMPLES:
216                 prefix = "stack-samples";
217                 break;
218             case HEAP_DUMP:
219                 prefix = "heap-dump";
220                 break;
221             case UNKNOWN:
222             default:
223                 prefix = "recording";
224                 break;
225         }
226         String format = "yyyy-MM-dd-HH-mm-ss";
227         String now = new SimpleDateFormat(format, Locale.US).format(new Date());
228         return String.format("%s-%s-%s-%s.%s", prefix, Build.BOARD, Build.ID, now,
229             mTraceEngine.getOutputExtension());
230     }
231 
getRecoveredFilename()232     public static String getRecoveredFilename() {
233         // Knowing what the previous Traceur session was recording would require adding a
234         // recordingWasTrace parameter to TraceUtils.traceStart().
235         return "recovered-" + getOutputFilename(RecordingType.UNKNOWN);
236     }
237 
getOutputFile(String filename)238     public static File getOutputFile(String filename) {
239         return new File(TraceUtils.TRACE_DIRECTORY, filename);
240     }
241 
cleanupOlderFiles()242     protected static void cleanupOlderFiles() {
243         FutureTask<Void> task = new FutureTask<Void>(
244                 () -> {
245                     try {
246                         FileUtils.deleteOlderFiles(new File(TRACE_DIRECTORY),
247                                 MIN_KEEP_COUNT, MIN_KEEP_AGE);
248                     } catch (RuntimeException e) {
249                         Log.e(TAG, "Failed to delete older traces", e);
250                     }
251                     return null;
252                 });
253         ExecutorService executor = Executors.newSingleThreadExecutor();
254         // execute() instead of submit() because we don't need the result.
255         executor.execute(task);
256     }
257 
getRunningAppProcesses(Context context)258     static Set<String> getRunningAppProcesses(Context context) {
259         ActivityManager am = context.getSystemService(ActivityManager.class);
260         List<ActivityManager.RunningAppProcessInfo> processes =
261                 am.getRunningAppProcesses();
262         // AM will return null instead of an empty list if no apps are found.
263         if (processes == null) {
264             return Collections.emptySet();
265         }
266 
267         Set<String> processNames = processes.stream()
268                 .map(process -> process.processName)
269                 .collect(Collectors.toSet());
270 
271         return processNames;
272     }
273 
274     /**
275      * Streams data from an InputStream to an OutputStream
276      */
277     static class Streamer {
278         private boolean mDone;
279 
Streamer(final String tag, final InputStream in, final OutputStream out)280         Streamer(final String tag, final InputStream in, final OutputStream out) {
281             new Thread(tag) {
282                 @Override
283                 public void run() {
284                     int read;
285                     byte[] buf = new byte[2 << 10];
286                     try {
287                         while ((read = in.read(buf)) != -1) {
288                             out.write(buf, 0, read);
289                         }
290                     } catch (IOException e) {
291                         Log.e(TAG, "Error while streaming " + tag);
292                     } finally {
293                         try {
294                             out.close();
295                         } catch (IOException e) {
296                             // Welp.
297                         }
298                         synchronized (Streamer.this) {
299                             mDone = true;
300                             Streamer.this.notify();
301                         }
302                     }
303                 }
304             }.start();
305         }
306 
isDone()307         synchronized boolean isDone() {
308             return mDone;
309         }
310 
waitForDone()311         synchronized void waitForDone() {
312             while (!isDone()) {
313                 try {
314                     wait();
315                 } catch (InterruptedException e) {
316                     Thread.currentThread().interrupt();
317                 }
318             }
319         }
320     }
321 
322     /**
323      * Redirects an InputStream to logcat.
324      */
325     private static class Logger {
326 
Logger(final String tag, final InputStream in)327         Logger(final String tag, final InputStream in) {
328             new Thread(tag) {
329                 @Override
330                 public void run() {
331                     String line;
332                     BufferedReader r = new BufferedReader(new InputStreamReader(in));
333                     try {
334                         while ((line = r.readLine()) != null) {
335                             Log.e(TAG, tag + ": " + line);
336                         }
337                     } catch (IOException e) {
338                         Log.e(TAG, "Error while streaming " + tag);
339                     } finally {
340                         try {
341                             r.close();
342                         } catch (IOException e) {
343                             // Welp.
344                         }
345                     }
346                 }
347             }.start();
348         }
349     }
350 }
351