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