• 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.os.Build;
20 import android.os.FileUtils;
21 import android.util.Log;
22 
23 import java.io.BufferedReader;
24 import java.io.File;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.InputStreamReader;
28 import java.io.OutputStream;
29 import java.text.SimpleDateFormat;
30 import java.util.Arrays;
31 import java.util.Collection;
32 import java.util.Date;
33 import java.util.Locale;
34 import java.util.TreeMap;
35 import java.util.concurrent.ExecutorService;
36 import java.util.concurrent.Executors;
37 import java.util.concurrent.FutureTask;
38 import java.util.concurrent.TimeUnit;
39 
40 /**
41  * Utility functions for tracing.
42  */
43 public class TraceUtils {
44 
45     static final String TAG = "Traceur";
46 
47     public static final String TRACE_DIRECTORY = "/data/local/traces/";
48 
49     private static TraceEngine mTraceEngine = new PerfettoUtils();
50 
51     private static final Runtime RUNTIME = Runtime.getRuntime();
52     private static final int PROCESS_TIMEOUT_MS = 30000; // 30 seconds
53 
54     enum RecordingType {
55       UNKNOWN, TRACE, STACK_SAMPLES
56     }
57     public interface TraceEngine {
getName()58         public String getName();
getOutputExtension()59         public String getOutputExtension();
traceStart(Collection<String> tags, int bufferSizeKb, boolean apps, boolean attachToBugreport, boolean longTrace, int maxLongTraceSizeMb, int maxLongTraceDurationMinutes)60         public boolean traceStart(Collection<String> tags, int bufferSizeKb, boolean apps,
61             boolean attachToBugreport, boolean longTrace, int maxLongTraceSizeMb,
62             int maxLongTraceDurationMinutes);
stackSampleStart(boolean attachToBugreport)63         public boolean stackSampleStart(boolean attachToBugreport);
traceStop()64         public void traceStop();
traceDump(File outFile)65         public boolean traceDump(File outFile);
isTracingOn()66         public boolean isTracingOn();
67     }
68 
currentTraceEngine()69     public static String currentTraceEngine() {
70         return mTraceEngine.getName();
71     }
72 
traceStart(Collection<String> tags, int bufferSizeKb, boolean apps, boolean longTrace, boolean attachToBugreport, int maxLongTraceSizeMb, int maxLongTraceDurationMinutes)73     public static boolean traceStart(Collection<String> tags, int bufferSizeKb, boolean apps,
74             boolean longTrace, boolean attachToBugreport, int maxLongTraceSizeMb,
75             int maxLongTraceDurationMinutes) {
76         return mTraceEngine.traceStart(tags, bufferSizeKb, apps,
77             attachToBugreport, longTrace, maxLongTraceSizeMb, maxLongTraceDurationMinutes);
78     }
79 
stackSampleStart(boolean attachToBugreport)80     public static boolean stackSampleStart(boolean attachToBugreport) {
81         return mTraceEngine.stackSampleStart(attachToBugreport);
82     }
83 
traceStop()84     public static void traceStop() {
85         mTraceEngine.traceStop();
86     }
87 
traceDump(File outFile)88     public static boolean traceDump(File outFile) {
89         return mTraceEngine.traceDump(outFile);
90     }
91 
isTracingOn()92     public static boolean isTracingOn() {
93         return mTraceEngine.isTracingOn();
94     }
95 
listCategories()96     public static TreeMap<String, String> listCategories() {
97         TreeMap<String, String> categories = PerfettoUtils.perfettoListCategories();
98         categories.put("sys_stats", "meminfo and vmstats");
99         categories.put("logs", "android logcat");
100         categories.put("cpu", "callstack samples");
101         return categories;
102     }
103 
clearSavedTraces()104     public static void clearSavedTraces() {
105         String cmd = "rm -f " + TRACE_DIRECTORY + "trace-*.*trace " +
106                 TRACE_DIRECTORY + "recovered-trace*.*trace " +
107                 TRACE_DIRECTORY + "stack-samples*.*trace";
108 
109         Log.v(TAG, "Clearing trace directory: " + cmd);
110         try {
111             Process rm = exec(cmd);
112 
113             if (rm.waitFor() != 0) {
114                 Log.e(TAG, "clearSavedTraces failed with: " + rm.exitValue());
115             }
116         } catch (Exception e) {
117             throw new RuntimeException(e);
118         }
119     }
120 
exec(String cmd)121     public static Process exec(String cmd) throws IOException {
122         return exec(cmd, null);
123     }
124 
exec(String cmd, String tmpdir)125     public static Process exec(String cmd, String tmpdir) throws IOException {
126         return exec(cmd, tmpdir, true);
127     }
128 
exec(String cmd, String tmpdir, boolean logOutput)129     public static Process exec(String cmd, String tmpdir, boolean logOutput) throws IOException {
130         String[] cmdarray = {"sh", "-c", cmd};
131         String[] envp = {"TMPDIR=" + tmpdir};
132         envp = tmpdir == null ? null : envp;
133 
134         Log.v(TAG, "exec: " + Arrays.toString(envp) + " " + Arrays.toString(cmdarray));
135 
136         Process process = RUNTIME.exec(cmdarray, envp);
137         new Logger("traceService:stderr", process.getErrorStream());
138         if (logOutput) {
139             new Logger("traceService:stdout", process.getInputStream());
140         }
141 
142         return process;
143     }
144 
145     // Returns the Process if the command terminated on time and null if not.
execWithTimeout(String cmd, String tmpdir, long timeout)146     public static Process execWithTimeout(String cmd, String tmpdir, long timeout)
147             throws IOException {
148         Process process = exec(cmd, tmpdir, true);
149         try {
150             if (!process.waitFor(timeout, TimeUnit.MILLISECONDS)) {
151                 Log.e(TAG, "Command '" + cmd + "' has timed out after " + timeout + " ms.");
152                 process.destroyForcibly();
153                 // Return null to signal a timeout and that the Process was destroyed.
154                 return null;
155             }
156         } catch (Exception e) {
157             throw new RuntimeException(e);
158         }
159         return process;
160     }
161 
getOutputFilename(RecordingType type)162     public static String getOutputFilename(RecordingType type) {
163         String prefix;
164         switch (type) {
165             case TRACE:
166                 prefix = "trace";
167                 break;
168             case STACK_SAMPLES:
169                 prefix = "stack-samples";
170                 break;
171             case UNKNOWN:
172             default:
173                 prefix = "recording";
174                 break;
175         }
176         String format = "yyyy-MM-dd-HH-mm-ss";
177         String now = new SimpleDateFormat(format, Locale.US).format(new Date());
178         return String.format("%s-%s-%s-%s.%s", prefix, Build.BOARD, Build.ID, now,
179             mTraceEngine.getOutputExtension());
180     }
181 
getRecoveredFilename()182     public static String getRecoveredFilename() {
183         // Knowing what the previous Traceur session was recording would require adding a
184         // recordingWasTrace parameter to TraceUtils.traceStart().
185         return "recovered-" + getOutputFilename(RecordingType.UNKNOWN);
186     }
187 
getOutputFile(String filename)188     public static File getOutputFile(String filename) {
189         return new File(TraceUtils.TRACE_DIRECTORY, filename);
190     }
191 
cleanupOlderFiles(final int minCount, final long minAge)192     protected static void cleanupOlderFiles(final int minCount, final long minAge) {
193         FutureTask<Void> task = new FutureTask<Void>(
194                 () -> {
195                     try {
196                         FileUtils.deleteOlderFiles(new File(TRACE_DIRECTORY), minCount, minAge);
197                     } catch (RuntimeException e) {
198                         Log.e(TAG, "Failed to delete older traces", e);
199                     }
200                     return null;
201                 });
202         ExecutorService executor = Executors.newSingleThreadExecutor();
203         // execute() instead of submit() because we don't need the result.
204         executor.execute(task);
205     }
206 
207     /**
208      * Streams data from an InputStream to an OutputStream
209      */
210     static class Streamer {
211         private boolean mDone;
212 
Streamer(final String tag, final InputStream in, final OutputStream out)213         Streamer(final String tag, final InputStream in, final OutputStream out) {
214             new Thread(tag) {
215                 @Override
216                 public void run() {
217                     int read;
218                     byte[] buf = new byte[2 << 10];
219                     try {
220                         while ((read = in.read(buf)) != -1) {
221                             out.write(buf, 0, read);
222                         }
223                     } catch (IOException e) {
224                         Log.e(TAG, "Error while streaming " + tag);
225                     } finally {
226                         try {
227                             out.close();
228                         } catch (IOException e) {
229                             // Welp.
230                         }
231                         synchronized (Streamer.this) {
232                             mDone = true;
233                             Streamer.this.notify();
234                         }
235                     }
236                 }
237             }.start();
238         }
239 
isDone()240         synchronized boolean isDone() {
241             return mDone;
242         }
243 
waitForDone()244         synchronized void waitForDone() {
245             while (!isDone()) {
246                 try {
247                     wait();
248                 } catch (InterruptedException e) {
249                     Thread.currentThread().interrupt();
250                 }
251             }
252         }
253     }
254 
255     /**
256      * Redirects an InputStream to logcat.
257      */
258     private static class Logger {
259 
Logger(final String tag, final InputStream in)260         Logger(final String tag, final InputStream in) {
261             new Thread(tag) {
262                 @Override
263                 public void run() {
264                     String line;
265                     BufferedReader r = new BufferedReader(new InputStreamReader(in));
266                     try {
267                         while ((line = r.readLine()) != null) {
268                             Log.e(TAG, tag + ": " + line);
269                         }
270                     } catch (IOException e) {
271                         Log.e(TAG, "Error while streaming " + tag);
272                     } finally {
273                         try {
274                             r.close();
275                         } catch (IOException e) {
276                             // Welp.
277                         }
278                     }
279                 }
280             }.start();
281         }
282     }
283 }
284