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.sysprop.TraceProperties;
20 import android.text.TextUtils;
21 import android.util.Log;
22 
23 import java.io.BufferedReader;
24 import java.io.File;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.InputStreamReader;
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.util.Collection;
34 import java.util.List;
35 import java.util.TreeMap;
36 
37 /**
38  * Utility functions for calling atrace
39  */
40 public class AtraceUtils implements TraceUtils.TraceEngine {
41 
42     static final String TAG = "Traceur";
43 
44     private static final String DEBUG_TRACING_FILE = "/sys/kernel/debug/tracing/tracing_on";
45     private static final String TRACING_FILE = "/sys/kernel/tracing/tracing_on";
46 
47     public static String NAME = "ATRACE";
48     private static String OUTPUT_EXTENSION = "ctrace";
49 
getName()50     public String getName() {
51         return NAME;
52     }
53 
getOutputExtension()54     public String getOutputExtension() {
55         return OUTPUT_EXTENSION;
56     }
57 
58     /* Note: longTrace and maxLongTrace* parameters are ignored in atrace mode. */
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) {
61 
62         String appParameter = apps ? "-a '*' " : "";
63         String cmd = "atrace --async_start -c -b " + bufferSizeKb + " "
64             + appParameter + TextUtils.join(" ", tags);
65 
66         Log.v(TAG, "Starting async atrace: " + cmd);
67         try {
68             Process atrace = TraceUtils.exec(cmd);
69             if (atrace.waitFor() != 0) {
70                 Log.e(TAG, "atraceStart failed with: " + atrace.exitValue());
71                 return false;
72             }
73         } catch (Exception e) {
74             throw new RuntimeException(e);
75         }
76         return true;
77     }
78 
traceStop()79     public void traceStop() {
80         String cmd = "atrace --async_stop > /dev/null";
81 
82         Log.v(TAG, "Stopping async atrace: " + cmd);
83         try {
84             Process atrace = TraceUtils.exec(cmd);
85 
86             if (atrace.waitFor() != 0) {
87                 Log.e(TAG, "atraceStop failed with: " + atrace.exitValue());
88             }
89         } catch (Exception e) {
90             throw new RuntimeException(e);
91         }
92     }
93 
traceDump(File outFile)94     public boolean traceDump(File outFile) {
95         String cmd = "atrace --async_stop -z -c -o " + outFile;
96 
97         Log.v(TAG, "Dumping async atrace: " + cmd);
98         try {
99             Process atrace = TraceUtils.exec(cmd);
100 
101             if (atrace.waitFor() != 0) {
102                 Log.e(TAG, "atraceDump failed with: " + atrace.exitValue());
103                 return false;
104             }
105 
106             Process ps = TraceUtils.exec("ps -AT");
107 
108             new Streamer("atraceDump:ps:stdout",
109                     ps.getInputStream(), new FileOutputStream(outFile, true /* append */));
110 
111             if (ps.waitFor() != 0) {
112                 Log.e(TAG, "atraceDump:ps failed with: " + ps.exitValue());
113                 return false;
114             }
115 
116             // Set the new file world readable to allow it to be adb pulled.
117             outFile.setReadable(true, false); // (readable, ownerOnly)
118             outFile.setWritable(true, false); // (readable, ownerOnly)
119         } catch (Exception e) {
120             throw new RuntimeException(e);
121         }
122         return true;
123     }
124 
isTracingOn()125     public boolean isTracingOn() {
126         boolean userInitiatedTracingFlag =
127             TraceProperties.user_initiated().orElse(false);
128 
129         if (!userInitiatedTracingFlag) {
130             return false;
131         }
132 
133         boolean tracingOnFlag = false;
134 
135         try {
136             List<String> tracingOnContents;
137 
138             Path debugTracingOnPath = Paths.get(DEBUG_TRACING_FILE);
139             Path tracingOnPath = Paths.get(TRACING_FILE);
140 
141             if (Files.isReadable(debugTracingOnPath)) {
142                 tracingOnContents = Files.readAllLines(debugTracingOnPath);
143             } else if (Files.isReadable(tracingOnPath)) {
144                 tracingOnContents = Files.readAllLines(tracingOnPath);
145             } else {
146                 return false;
147             }
148 
149             tracingOnFlag = !tracingOnContents.get(0).equals("0");
150         } catch (IOException e) {
151             throw new RuntimeException(e);
152         }
153 
154         return userInitiatedTracingFlag && tracingOnFlag;
155     }
156 
atraceListCategories()157     public static TreeMap<String,String> atraceListCategories() {
158         String cmd = "atrace --list_categories";
159 
160         Log.v(TAG, "Listing tags: " + cmd);
161         try {
162             Process atrace = TraceUtils.exec(cmd);
163 
164             new Logger("atraceListCat:stderr", atrace.getErrorStream());
165             BufferedReader stdout = new BufferedReader(
166                     new InputStreamReader(atrace.getInputStream()));
167 
168             if (atrace.waitFor() != 0) {
169                 Log.e(TAG, "atraceListCategories failed with: " + atrace.exitValue());
170             }
171 
172             TreeMap<String, String> result = new TreeMap<>();
173             String line;
174             while ((line = stdout.readLine()) != null) {
175                 String[] fields = line.trim().split(" - ", 2);
176                 if (fields.length == 2) {
177                     result.put(fields[0], fields[1]);
178                 }
179             }
180             return result;
181         } catch (Exception e) {
182             throw new RuntimeException(e);
183         }
184     }
185 
186 
187     /**
188      * Streams data from an InputStream to an OutputStream
189      */
190     private static class Streamer {
191         private boolean mDone;
192 
Streamer(final String tag, final InputStream in, final OutputStream out)193         Streamer(final String tag, final InputStream in, final OutputStream out) {
194             new Thread(tag) {
195                 @Override
196                 public void run() {
197                     int read;
198                     byte[] buf = new byte[2 << 10];
199                     try {
200                         while ((read = in.read(buf)) != -1) {
201                             out.write(buf, 0, read);
202                         }
203                     } catch (IOException e) {
204                         Log.e(TAG, "Error while streaming " + tag);
205                     } finally {
206                         try {
207                             out.close();
208                         } catch (IOException e) {
209                             // Welp.
210                         }
211                         synchronized (Streamer.this) {
212                             mDone = true;
213                             Streamer.this.notify();
214                         }
215                     }
216                 }
217             }.start();
218         }
219 
isDone()220         synchronized boolean isDone() {
221             return mDone;
222         }
223 
waitForDone()224         synchronized void waitForDone() {
225             while (!isDone()) {
226                 try {
227                     wait();
228                 } catch (InterruptedException e) {
229                     Thread.currentThread().interrupt();
230                 }
231             }
232         }
233     }
234 
235     /**
236      * Streams data from an InputStream to an OutputStream
237      */
238     private static class Logger {
239 
Logger(final String tag, final InputStream in)240         Logger(final String tag, final InputStream in) {
241             new Thread(tag) {
242                 @Override
243                 public void run() {
244                     int read;
245                     String line;
246                     BufferedReader r = new BufferedReader(new InputStreamReader(in));
247                     try {
248                         while ((line = r.readLine()) != null) {
249                             Log.e(TAG, tag + ": " + line);
250                         }
251                     } catch (IOException e) {
252                         Log.e(TAG, "Error while streaming " + tag);
253                     } finally {
254                         try {
255                             r.close();
256                         } catch (IOException e) {
257                             // Welp.
258                         }
259                     }
260                 }
261             }.start();
262         }
263     }
264 }
265