• 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.sysprop.TraceProperties;
20 import android.system.Os;
21 import android.util.Log;
22 
23 import java.io.File;
24 import java.io.IOException;
25 import java.nio.file.Files;
26 import java.nio.file.Path;
27 import java.nio.file.Paths;
28 import java.util.Collection;
29 import java.util.concurrent.TimeUnit;
30 
31 /**
32  * Utility functions for calling Perfetto
33  */
34 public class PerfettoUtils implements TraceUtils.TraceEngine {
35 
36     static final String TAG = "Traceur";
37     public static final String NAME = "PERFETTO";
38 
39     private static final String OUTPUT_EXTENSION = "perfetto-trace";
40     private static final String TEMP_DIR= "/data/local/traces/";
41     private static final String TEMP_TRACE_LOCATION = "/data/local/traces/.trace-in-progress.trace";
42 
43     private static final String PERFETTO_TAG = "traceur";
44     private static final String MARKER = "PERFETTO_ARGUMENTS";
45     private static final int STARTUP_TIMEOUT_MS = 10000;
46     private static final long MEGABYTES_TO_BYTES = 1024L * 1024L;
47     private static final long MINUTES_TO_MILLISECONDS = 60L * 1000L;
48 
49     private static final String POWER_TAG = "power";
50     private static final String MEMORY_TAG = "memory";
51 
getName()52     public String getName() {
53         return NAME;
54     }
55 
getOutputExtension()56     public String getOutputExtension() {
57         return OUTPUT_EXTENSION;
58     }
59 
traceStart(Collection<String> tags, int bufferSizeKb, boolean apps, boolean longTrace, int maxLongTraceSizeMb, int maxLongTraceDurationMinutes)60     public boolean traceStart(Collection<String> tags, int bufferSizeKb, boolean apps,
61             boolean longTrace, int maxLongTraceSizeMb, int maxLongTraceDurationMinutes) {
62         // If setprop persist.traced.enable isn't set, the perfetto traced service
63         // is not enabled on this device. If the user wants to trace, we should enable
64         // this service. Since it's such a low-overhead service, we will leave it enabled
65         // subsequently.
66         boolean perfettoEnabled = TraceProperties.enable().orElse(false);
67         if (!perfettoEnabled) {
68             Log.e(TAG, "Starting the traced service to allow Perfetto to trace.");
69             TraceProperties.enable(true);
70         }
71 
72         if (isTracingOn()) {
73             Log.e(TAG, "Attempting to start perfetto trace but trace is already in progress");
74             return false;
75         } else {
76             // Ensure the temporary trace file is cleared.
77             try {
78                 Files.deleteIfExists(Paths.get(TEMP_TRACE_LOCATION));
79             } catch (Exception e) {
80                 throw new RuntimeException(e);
81             }
82         }
83 
84         // The user chooses a per-CPU buffer size due to atrace limitations.
85         // So we use this to ensure that we reserve the correctly-sized buffer.
86         int numCpus = Runtime.getRuntime().availableProcessors();
87 
88         // Build the perfetto config that will be passed on the command line.
89         StringBuilder config = new StringBuilder()
90             .append("write_into_file: true\n")
91             // Ensure that we flush ftrace data every 30s even if cpus are idle.
92             .append("flush_period_ms: 30000\n");
93 
94             // If we have set one of the long trace parameters, we must also
95             // tell Perfetto to notify Traceur when the long trace is done.
96             if (longTrace) {
97                 config.append("notify_traceur: true\n");
98 
99                 if (maxLongTraceSizeMb != 0) {
100                     config.append("max_file_size_bytes: "
101                         + (maxLongTraceSizeMb * MEGABYTES_TO_BYTES) + "\n");
102                 }
103 
104                 if (maxLongTraceDurationMinutes != 0) {
105                     config.append("duration_ms: "
106                         + (maxLongTraceDurationMinutes * MINUTES_TO_MILLISECONDS)
107                         + "\n");
108                 }
109 
110                 // Default value for long traces to write to file.
111                 config.append("file_write_period_ms: 1000\n");
112             } else {
113                 // For short traces, we don't write to the file.
114                 // So, always use the maximum value here: 7 days.
115                 config.append("file_write_period_ms: 604800000\n");
116             }
117 
118         config.append("incremental_state_config {\n")
119             .append("  clear_period_ms: 15000\n")
120             .append("} \n")
121             // This is target_buffer: 0, which is used for ftrace.
122             .append("buffers {\n")
123             .append("  size_kb: " + bufferSizeKb * numCpus + "\n")
124             .append("  fill_policy: RING_BUFFER\n")
125             .append("} \n")
126             // This is target_buffer: 1, which is used for additional data sources.
127             .append("buffers {\n")
128             .append("  size_kb: 2048\n")
129             .append("  fill_policy: RING_BUFFER\n")
130             .append("} \n")
131             .append("data_sources {\n")
132             .append("  config {\n")
133             .append("    name: \"linux.ftrace\"\n")
134             .append("    target_buffer: 0\n")
135             .append("    ftrace_config {\n");
136 
137         for (String tag : tags) {
138             // Tags are expected to be only letters, numbers, and underscores.
139             String cleanTag = tag.replaceAll("[^a-zA-Z0-9_]", "");
140             if (!cleanTag.equals(tag)) {
141                 Log.w(TAG, "Attempting to use an invalid tag: " + tag);
142             }
143             config.append("      atrace_categories: \"" + cleanTag + "\"\n");
144         }
145 
146         if (apps) {
147             config.append("      atrace_apps: \"*\"\n");
148         }
149 
150         // These parameters affect only the kernel trace buffer size and how
151         // frequently it gets moved into the userspace buffer defined above.
152         config.append("      buffer_size_kb: 8192\n")
153             .append("      drain_period_ms: 1000\n")
154             .append("    }\n")
155             .append("  }\n")
156             .append("}\n")
157             .append(" \n");
158 
159         // For process association. If the memory tag is enabled,
160         // poll periodically instead of just once at the beginning.
161         config.append("data_sources {\n")
162             .append("  config {\n")
163             .append("    name: \"linux.process_stats\"\n")
164             .append("    target_buffer: 1\n");
165         if (tags.contains(MEMORY_TAG)) {
166             config.append("    process_stats_config {\n")
167                 .append("      proc_stats_poll_ms: 60000\n")
168                 .append("    }\n");
169         }
170         config.append("  }\n")
171             .append("} \n");
172 
173         if (tags.contains(POWER_TAG)) {
174             config.append("data_sources: {\n")
175                 .append("  config { \n")
176                 .append("    name: \"android.power\"\n")
177                 .append("    target_buffer: 1\n")
178                 .append("    android_power_config {\n");
179             if (longTrace) {
180                 config.append("      battery_poll_ms: 5000\n");
181             } else {
182                 config.append("      battery_poll_ms: 1000\n");
183             }
184             config.append("      collect_power_rails: true\n")
185                 .append("      battery_counters: BATTERY_COUNTER_CAPACITY_PERCENT\n")
186                 .append("      battery_counters: BATTERY_COUNTER_CHARGE\n")
187                 .append("      battery_counters: BATTERY_COUNTER_CURRENT\n")
188                 .append("    }\n")
189                 .append("  }\n")
190                 .append("}\n");
191         }
192 
193         String configString = config.toString();
194 
195         // If the here-doc ends early, within the config string, exit immediately.
196         // This should never happen.
197         if (configString.contains(MARKER)) {
198             throw new RuntimeException("The arguments to the Perfetto command are malformed.");
199         }
200 
201         String cmd = "perfetto --detach=" + PERFETTO_TAG
202             + " -o " + TEMP_TRACE_LOCATION
203             + " -c - --txt"
204             + " <<" + MARKER +"\n" + configString + "\n" + MARKER;
205 
206         Log.v(TAG, "Starting perfetto trace.");
207         try {
208             Process process = TraceUtils.exec(cmd, TEMP_DIR);
209 
210             // If we time out, ensure that the perfetto process is destroyed.
211             if (!process.waitFor(STARTUP_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
212                 Log.e(TAG, "perfetto traceStart has timed out after "
213                     + STARTUP_TIMEOUT_MS + " ms.");
214                 process.destroyForcibly();
215                 return false;
216             }
217 
218             if (process.exitValue() != 0) {
219                 Log.e(TAG, "perfetto traceStart failed with: "
220                     + process.exitValue());
221                 return false;
222             }
223         } catch (Exception e) {
224             throw new RuntimeException(e);
225         }
226 
227         Log.v(TAG, "perfetto traceStart succeeded!");
228         return true;
229     }
230 
traceStop()231     public void traceStop() {
232         Log.v(TAG, "Stopping perfetto trace.");
233 
234         if (!isTracingOn()) {
235             Log.w(TAG, "No trace appears to be in progress. Stopping perfetto trace may not work.");
236         }
237 
238         String cmd = "perfetto --stop --attach=" + PERFETTO_TAG;
239         try {
240             Process process = TraceUtils.exec(cmd);
241             if (process.waitFor() != 0) {
242                 Log.e(TAG, "perfetto traceStop failed with: " + process.exitValue());
243             }
244         } catch (Exception e) {
245             throw new RuntimeException(e);
246         }
247     }
248 
traceDump(File outFile)249     public boolean traceDump(File outFile) {
250         traceStop();
251 
252         // Short-circuit if the file we're trying to dump to doesn't exist.
253         if (!Files.exists(Paths.get(TEMP_TRACE_LOCATION))) {
254             Log.e(TAG, "In-progress trace file doesn't exist, aborting trace dump.");
255             return false;
256         }
257 
258         Log.v(TAG, "Saving perfetto trace to " + outFile);
259 
260         try {
261             Os.rename(TEMP_TRACE_LOCATION, outFile.getCanonicalPath());
262         } catch (Exception e) {
263             throw new RuntimeException(e);
264         }
265 
266         outFile.setReadable(true, false); // (readable, ownerOnly)
267         return true;
268     }
269 
isTracingOn()270     public boolean isTracingOn() {
271         // If setprop persist.traced.enable isn't set, the perfetto traced service
272         // is not enabled on this device. When we start a trace for the first time,
273         // we'll enable it; if it's not enabled we know tracing is not on.
274         // Without this property set we can't query perfetto for an existing trace.
275         boolean perfettoEnabled = TraceProperties.enable().orElse(false);
276         if (!perfettoEnabled) {
277             return false;
278         }
279 
280         String cmd = "perfetto --is_detached=" + PERFETTO_TAG;
281 
282         try {
283             Process process = TraceUtils.exec(cmd);
284 
285             // 0 represents a detached process exists with this name
286             // 2 represents no detached process with this name
287             // 1 (or other error code) represents an error
288             int result = process.waitFor();
289             if (result == 0) {
290                 return true;
291             } else if (result == 2) {
292                 return false;
293             } else {
294                 throw new RuntimeException("Perfetto error: " + result);
295             }
296         } catch (Exception e) {
297             throw new RuntimeException(e);
298         }
299     }
300 }
301