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.AsyncTask;
21 import android.os.FileUtils;
22 import android.util.Log;
23 
24 import java.io.BufferedReader;
25 import java.io.File;
26 import java.io.InputStream;
27 import java.io.InputStreamReader;
28 import java.io.IOException;
29 import java.io.OutputStream;
30 import java.nio.file.Files;
31 import java.nio.file.Path;
32 import java.nio.file.Paths;
33 import java.text.SimpleDateFormat;
34 import java.util.Arrays;
35 import java.util.Date;
36 import java.util.Locale;
37 import java.util.Collection;
38 import java.util.TreeMap;
39 
40 /**
41  * Utility functions for tracing.
42  * Will call atrace or perfetto depending on the setting.
43  */
44 public class TraceUtils {
45 
46     static final String TAG = "Traceur";
47 
48     public static final String TRACE_DIRECTORY = "/data/local/traces/";
49 
50     // To change Traceur to use atrace to collect traces,
51     // change mTraceEngine to point to AtraceUtils().
52     private static TraceEngine mTraceEngine = new PerfettoUtils();
53 
54     private static final Runtime RUNTIME = Runtime.getRuntime();
55 
56     public interface TraceEngine {
getName()57         public String getName();
getOutputExtension()58         public String getOutputExtension();
traceStart(Collection<String> tags, int bufferSizeKb, boolean apps, boolean longTrace, int maxLongTraceSizeMb, int maxLongTraceDurationMinutes)59         public boolean traceStart(Collection<String> tags, int bufferSizeKb, boolean apps,
60             boolean longTrace, int maxLongTraceSizeMb, int maxLongTraceDurationMinutes);
traceStop()61         public void traceStop();
traceDump(File outFile)62         public boolean traceDump(File outFile);
isTracingOn()63         public boolean isTracingOn();
64     }
65 
currentTraceEngine()66     public static String currentTraceEngine() {
67         return mTraceEngine.getName();
68     }
69 
traceStart(Collection<String> tags, int bufferSizeKb, boolean apps, boolean longTrace, int maxLongTraceSizeMb, int maxLongTraceDurationMinutes)70     public static boolean traceStart(Collection<String> tags, int bufferSizeKb, boolean apps,
71             boolean longTrace, int maxLongTraceSizeMb, int maxLongTraceDurationMinutes) {
72         return mTraceEngine.traceStart(tags, bufferSizeKb, apps,
73             longTrace, maxLongTraceSizeMb, maxLongTraceDurationMinutes);
74     }
75 
traceStop()76     public static void traceStop() {
77         mTraceEngine.traceStop();
78     }
79 
traceDump(File outFile)80     public static boolean traceDump(File outFile) {
81         return mTraceEngine.traceDump(outFile);
82     }
83 
isTracingOn()84     public static boolean isTracingOn() {
85         return mTraceEngine.isTracingOn();
86     }
87 
listCategories()88     public static TreeMap<String, String> listCategories() {
89         return AtraceUtils.atraceListCategories();
90     }
91 
clearSavedTraces()92     public static void clearSavedTraces() {
93         String cmd = "rm -f " + TRACE_DIRECTORY + "trace-*.*trace";
94 
95         Log.v(TAG, "Clearing trace directory: " + cmd);
96         try {
97             Process rm = exec(cmd);
98 
99             if (rm.waitFor() != 0) {
100                 Log.e(TAG, "clearSavedTraces failed with: " + rm.exitValue());
101             }
102         } catch (Exception e) {
103             throw new RuntimeException(e);
104         }
105     }
106 
exec(String cmd)107     public static Process exec(String cmd) throws IOException {
108         return exec(cmd, null);
109     }
110 
exec(String cmd, String tmpdir)111     public static Process exec(String cmd, String tmpdir) throws IOException {
112         return exec(cmd, tmpdir, true);
113     }
114 
exec(String cmd, String tmpdir, boolean logOutput)115     public static Process exec(String cmd, String tmpdir, boolean logOutput) throws IOException {
116         String[] cmdarray = {"sh", "-c", cmd};
117         String[] envp = {"TMPDIR=" + tmpdir};
118         envp = tmpdir == null ? null : envp;
119 
120         Log.v(TAG, "exec: " + Arrays.toString(envp) + " " + Arrays.toString(cmdarray));
121 
122         Process process = RUNTIME.exec(cmdarray, envp);
123         new Logger("traceService:stderr", process.getErrorStream());
124         if (logOutput) {
125             new Logger("traceService:stdout", process.getInputStream());
126         }
127 
128         return process;
129     }
130 
getOutputFilename()131     public static String getOutputFilename() {
132         String format = "yyyy-MM-dd-HH-mm-ss";
133         String now = new SimpleDateFormat(format, Locale.US).format(new Date());
134         return String.format("trace-%s-%s-%s.%s", Build.BOARD, Build.ID, now,
135             mTraceEngine.getOutputExtension());
136     }
137 
getOutputFile(String filename)138     public static File getOutputFile(String filename) {
139         return new File(TraceUtils.TRACE_DIRECTORY, filename);
140     }
141 
cleanupOlderFiles(final int minCount, final long minAge)142     protected static void cleanupOlderFiles(final int minCount, final long minAge) {
143         new AsyncTask<Void, Void, Void>() {
144             @Override
145             protected Void doInBackground(Void... params) {
146                 try {
147                     FileUtils.deleteOlderFiles(new File(TRACE_DIRECTORY), minCount, minAge);
148                 } catch (RuntimeException e) {
149                     Log.e(TAG, "Failed to delete older traces", e);
150                 }
151                 return null;
152             }
153         }.execute();
154     }
155 
156     /**
157      * Streams data from an InputStream to an OutputStream
158      */
159     static class Streamer {
160         private boolean mDone;
161 
Streamer(final String tag, final InputStream in, final OutputStream out)162         Streamer(final String tag, final InputStream in, final OutputStream out) {
163             new Thread(tag) {
164                 @Override
165                 public void run() {
166                     int read;
167                     byte[] buf = new byte[2 << 10];
168                     try {
169                         while ((read = in.read(buf)) != -1) {
170                             out.write(buf, 0, read);
171                         }
172                     } catch (IOException e) {
173                         Log.e(TAG, "Error while streaming " + tag);
174                     } finally {
175                         try {
176                             out.close();
177                         } catch (IOException e) {
178                             // Welp.
179                         }
180                         synchronized (Streamer.this) {
181                             mDone = true;
182                             Streamer.this.notify();
183                         }
184                     }
185                 }
186             }.start();
187         }
188 
isDone()189         synchronized boolean isDone() {
190             return mDone;
191         }
192 
waitForDone()193         synchronized void waitForDone() {
194             while (!isDone()) {
195                 try {
196                     wait();
197                 } catch (InterruptedException e) {
198                     Thread.currentThread().interrupt();
199                 }
200             }
201         }
202     }
203 
204     /**
205      * Streams data from an InputStream to an OutputStream
206      */
207     private static class Logger {
208 
Logger(final String tag, final InputStream in)209         Logger(final String tag, final InputStream in) {
210             new Thread(tag) {
211                 @Override
212                 public void run() {
213                     int read;
214                     String line;
215                     BufferedReader r = new BufferedReader(new InputStreamReader(in));
216                     try {
217                         while ((line = r.readLine()) != null) {
218                             Log.e(TAG, tag + ": " + line);
219                         }
220                     } catch (IOException e) {
221                         Log.e(TAG, "Error while streaming " + tag);
222                     } finally {
223                         try {
224                             r.close();
225                         } catch (IOException e) {
226                             // Welp.
227                         }
228                     }
229                 }
230             }.start();
231         }
232     }
233 }
234