• 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.system.Os;
20 import android.util.Log;
21 
22 import java.io.File;
23 import java.nio.file.Files;
24 import java.nio.file.Paths;
25 import java.util.Collection;
26 import java.util.List;
27 import java.util.TreeMap;
28 import java.util.concurrent.TimeUnit;
29 
30 import perfetto.protos.DataSourceDescriptorOuterClass.DataSourceDescriptor;
31 import perfetto.protos.FtraceDescriptorOuterClass.FtraceDescriptor.AtraceCategory;
32 import perfetto.protos.TracingServiceStateOuterClass.TracingServiceState;
33 import perfetto.protos.TracingServiceStateOuterClass.TracingServiceState.DataSource;
34 
35 /**
36  * Utility functions for calling Perfetto
37  */
38 public class PerfettoUtils implements TraceUtils.TraceEngine {
39 
40     static final String TAG = "Traceur";
41     public static final String NAME = "PERFETTO";
42 
43     private static final String OUTPUT_EXTENSION = "perfetto-trace";
44     private static final String TEMP_DIR = "/data/local/traces/";
45     private static final String TEMP_TRACE_LOCATION = "/data/local/traces/.trace-in-progress.trace";
46 
47     private static final String PERFETTO_TAG = "traceur";
48     private static final String MARKER = "PERFETTO_ARGUMENTS";
49     private static final int LIST_TIMEOUT_MS = 10000;
50     private static final int STARTUP_TIMEOUT_MS = 10000;
51     private static final int STOP_TIMEOUT_MS = 30000;
52     private static final long MEGABYTES_TO_BYTES = 1024L * 1024L;
53     private static final long MINUTES_TO_MILLISECONDS = 60L * 1000L;
54 
55     // The total amount of memory allocated to the two target buffers will be divided according to a
56     // ratio of (BUFFER_SIZE_RATIO - 1) to 1.
57     private static final int BUFFER_SIZE_RATIO = 32;
58 
59     // atrace trace categories that will result in added data sources in the Perfetto config.
60     private static final String CAMERA_TAG = "camera";
61     private static final String GFX_TAG = "gfx";
62     private static final String MEMORY_TAG = "memory";
63     private static final String NETWORK_TAG = "network";
64     private static final String POWER_TAG = "power";
65     private static final String SCHED_TAG = "sched";
66     private static final String WEBVIEW_TAG = "webview";
67 
68     // Custom trace categories.
69     private static final String SYS_STATS_TAG = "sys_stats";
70     private static final String LOG_TAG = "logs";
71     private static final String CPU_TAG = "cpu";
72 
getName()73     public String getName() {
74         return NAME;
75     }
76 
getOutputExtension()77     public String getOutputExtension() {
78         return OUTPUT_EXTENSION;
79     }
80 
traceStart(Collection<String> tags, int bufferSizeKb, boolean apps, boolean attachToBugreport, boolean longTrace, int maxLongTraceSizeMb, int maxLongTraceDurationMinutes)81     public boolean traceStart(Collection<String> tags, int bufferSizeKb, boolean apps,
82             boolean attachToBugreport, boolean longTrace, int maxLongTraceSizeMb,
83             int maxLongTraceDurationMinutes) {
84         if (isTracingOn()) {
85             Log.e(TAG, "Attempting to start perfetto trace but trace is already in progress");
86             return false;
87         } else {
88             recoverExistingRecording();
89         }
90 
91         StringBuilder config = new StringBuilder();
92         appendBaseConfigOptions(config, attachToBugreport, longTrace, maxLongTraceSizeMb,
93                 maxLongTraceDurationMinutes);
94 
95         // The user chooses a per-CPU buffer size due to atrace limitations.
96         // So we use this to ensure that we reserve the correctly-sized buffer.
97         int numCpus = Runtime.getRuntime().availableProcessors();
98 
99         // Allots 1 / BUFFER_SIZE_RATIO to the small buffer and the remainder to the large buffer.
100         int totalBufferSizeKb = numCpus * bufferSizeKb;
101         int targetBuffer1Kb = totalBufferSizeKb / BUFFER_SIZE_RATIO;
102         int targetBuffer0Kb = totalBufferSizeKb - targetBuffer1Kb;
103 
104         // This is target_buffer: 0, which is used for ftrace and the ftrace-derived
105         // android.gpu.memory.
106         appendTraceBuffer(config, targetBuffer0Kb);
107 
108         // This is target_buffer: 1, which is used for additional data sources.
109         appendTraceBuffer(config, targetBuffer1Kb);
110 
111         appendFtraceConfig(config, tags, apps);
112         appendProcStatsConfig(config, tags, /* target_buffer = */ 1);
113         appendAdditionalDataSources(config, tags, longTrace, /* target_buffer = */ 1);
114 
115         return startPerfettoWithConfig(config.toString());
116     }
117 
stackSampleStart(boolean attachToBugreport)118     public boolean stackSampleStart(boolean attachToBugreport) {
119         if (isTracingOn()) {
120             Log.e(TAG, "Attemping to start stack sampling but perfetto is already active");
121             return false;
122         } else {
123             recoverExistingRecording();
124         }
125 
126         StringBuilder config = new StringBuilder();
127         appendBaseConfigOptions(config, attachToBugreport, /* longTrace = */ false,
128                 /* maxLongTraceSizeMb */ 0, /* maxLongTraceDurationMinutes = */ 0);
129 
130         // Number of cores * 16MiB. 16MiB was chosen as it is the default for Traceur traces.
131         int targetBufferKb = Runtime.getRuntime().availableProcessors() * (16 * 1024);
132         appendTraceBuffer(config, targetBufferKb);
133 
134         appendLinuxPerfConfig(config, /* target_buffer = */ 0);
135         appendProcStatsConfig(config, /* tags = */ null, /* target_buffer = */ 0);
136 
137         return startPerfettoWithConfig(config.toString());
138     }
139 
traceStop()140     public void traceStop() {
141         Log.v(TAG, "Stopping perfetto trace.");
142 
143         if (!isTracingOn()) {
144             Log.w(TAG, "No trace appears to be in progress. Stopping perfetto trace may not work.");
145         }
146 
147         String cmd = "perfetto --stop --attach=" + PERFETTO_TAG;
148         try {
149             Process process = TraceUtils.execWithTimeout(cmd, null, STOP_TIMEOUT_MS);
150             if (process != null && process.exitValue() != 0) {
151                 Log.e(TAG, "perfetto traceStop failed with: " + process.exitValue());
152             }
153         } catch (Exception e) {
154             throw new RuntimeException(e);
155         }
156     }
157 
traceDump(File outFile)158     public boolean traceDump(File outFile) {
159         traceStop();
160 
161         // Short-circuit if a trace was not stopped.
162         if (isTracingOn()) {
163             Log.e(TAG, "Trace was not stopped successfully, aborting trace dump.");
164             return false;
165         }
166 
167         // Short-circuit if the file we're trying to dump to doesn't exist.
168         if (!Files.exists(Paths.get(TEMP_TRACE_LOCATION))) {
169             Log.e(TAG, "In-progress trace file doesn't exist, aborting trace dump.");
170             return false;
171         }
172 
173         Log.v(TAG, "Saving perfetto trace to " + outFile);
174 
175         try {
176             Os.rename(TEMP_TRACE_LOCATION, outFile.getCanonicalPath());
177         } catch (Exception e) {
178             throw new RuntimeException(e);
179         }
180 
181         outFile.setReadable(true, false); // (readable, ownerOnly)
182         outFile.setWritable(true, false); // (writable, ownerOnly)
183         return true;
184     }
185 
isTracingOn()186     public boolean isTracingOn() {
187         String cmd = "perfetto --is_detached=" + PERFETTO_TAG;
188 
189         try {
190             Process process = TraceUtils.exec(cmd);
191 
192             // 0 represents a detached process exists with this name
193             // 2 represents no detached process with this name
194             // 1 (or other error code) represents an error
195             int result = process.waitFor();
196             if (result == 0) {
197                 return true;
198             } else if (result == 2) {
199                 return false;
200             } else {
201                 throw new RuntimeException("Perfetto error: " + result);
202             }
203         } catch (Exception e) {
204             throw new RuntimeException(e);
205         }
206     }
207 
perfettoListCategories()208     public static TreeMap<String,String> perfettoListCategories() {
209         String cmd = "perfetto --query-raw";
210 
211         Log.v(TAG, "Listing tags: " + cmd);
212         try {
213 
214             TreeMap<String, String> result = new TreeMap<>();
215 
216             // execWithTimeout() cannot be used because stdout must be consumed before the process
217             // is terminated.
218             Process perfetto = TraceUtils.exec(cmd, null, false);
219             TracingServiceState serviceState =
220                     TracingServiceState.parseFrom(perfetto.getInputStream());
221 
222             // Destroy the perfetto process if it times out.
223             if (!perfetto.waitFor(LIST_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
224                 Log.e(TAG, "perfettoListCategories timed out after " + LIST_TIMEOUT_MS + " ms.");
225                 perfetto.destroyForcibly();
226                 return result;
227             }
228 
229             // The perfetto process completed and failed, but does not need to be destroyed.
230             if (perfetto.exitValue() != 0) {
231                 Log.e(TAG, "perfettoListCategories failed with: " + perfetto.exitValue());
232             }
233 
234             List<AtraceCategory> categories = null;
235 
236             for (DataSource dataSource : serviceState.getDataSourcesList()) {
237                 DataSourceDescriptor dataSrcDescriptor = dataSource.getDsDescriptor();
238                 if (dataSrcDescriptor.getName().equals("linux.ftrace")){
239                     categories = dataSrcDescriptor.getFtraceDescriptor().getAtraceCategoriesList();
240                     break;
241                 }
242             }
243 
244             if (categories != null) {
245                 for (AtraceCategory category : categories) {
246                     result.put(category.getName(), category.getDescription());
247                 }
248             }
249             return result;
250         } catch (Exception e) {
251             throw new RuntimeException(e);
252         }
253     }
254 
255     // Starts Perfetto with the provided config string.
startPerfettoWithConfig(String config)256     private boolean startPerfettoWithConfig(String config) {
257         // If the here-doc ends early, within the config string, exit immediately.
258         // This should never happen.
259         if (config.contains(MARKER)) {
260             throw new RuntimeException("The arguments to the Perfetto command are malformed.");
261         }
262 
263         String cmd = "perfetto --detach=" + PERFETTO_TAG
264                 + " -o " + TEMP_TRACE_LOCATION
265                 + " -c - --txt"
266                 + " <<" + MARKER +"\n" + config + "\n" + MARKER;
267 
268         Log.v(TAG, "Starting perfetto trace.");
269         try {
270             Process process = TraceUtils.execWithTimeout(cmd, TEMP_DIR, STARTUP_TIMEOUT_MS);
271             if (process == null) {
272                 return false;
273             } else if (process.exitValue() != 0) {
274                 Log.e(TAG, "perfetto trace start failed with: " + process.exitValue());
275                 return false;
276             }
277         } catch (Exception e) {
278             throw new RuntimeException(e);
279         }
280 
281         Log.v(TAG, "perfetto traceStart succeeded!");
282         return true;
283     }
284 
285     // Saves an existing temporary recording under a "recovered" filename.
recoverExistingRecording()286     private void recoverExistingRecording() {
287         File recoveredFile = TraceUtils.getOutputFile(
288                 TraceUtils.getRecoveredFilename());
289         if (!traceDump(recoveredFile)) {
290             Log.w(TAG, "Failed to recover in-progress trace.");
291         }
292     }
293 
294     // Appends options that can be used in any of Traceur's Perfetto configs.
appendBaseConfigOptions(StringBuilder config, boolean attachToBugreport, boolean longTrace, int maxLongTraceSizeMb, int maxLongTraceDurationMinutes)295     private void appendBaseConfigOptions(StringBuilder config, boolean attachToBugreport,
296             boolean longTrace, int maxLongTraceSizeMb, int maxLongTraceDurationMinutes) {
297         config.append("write_into_file: true\n");
298 
299         // Ensure that we flush ftrace every 30s even if cpus are idle.
300         config.append("flush_period_ms: 30000\n");
301 
302         // If the user has flagged that in-progress trace sessions should be grabbed during
303         // bugreports, and BetterBug is present.
304         if (attachToBugreport) {
305             config.append("bugreport_score: 500\n");
306         }
307 
308         // Indicates that Perfetto should notify Traceur if the tracing session's status changes.
309         config.append("notify_traceur: true\n");
310 
311         // Allow previous trace contents to be referenced instead of duplicating.
312         config.append("incremental_state_config {\n")
313             .append("  clear_period_ms: 15000\n")
314             .append("}\n");
315 
316         // Add long/short trace-specific options.
317         if (longTrace) {
318             if (maxLongTraceSizeMb != 0) {
319                 config.append("max_file_size_bytes: "
320                     + (maxLongTraceSizeMb * MEGABYTES_TO_BYTES) + "\n");
321             }
322             if (maxLongTraceDurationMinutes != 0) {
323                 config.append("duration_ms: "
324                     + (maxLongTraceDurationMinutes * MINUTES_TO_MILLISECONDS)
325                     + "\n");
326             }
327 
328             // Default value for long traces to write to file.
329             config.append("file_write_period_ms: 1000\n");
330         } else {
331             // For short traces, we don't write to the file.
332             // So, always use the maximum value here: 7 days.
333             config.append("file_write_period_ms: 604800000\n");
334         }
335     }
336 
337     // Specifies an additional buffer of size bufferSizeKb. Data sources can reference specific
338     // buffers in the order that they are added by this method.
appendTraceBuffer(StringBuilder config, int bufferSizeKb)339     private void appendTraceBuffer(StringBuilder config, int bufferSizeKb) {
340         config.append("buffers {\n")
341             .append("  size_kb: " + bufferSizeKb + "\n")
342             .append("  fill_policy: RING_BUFFER\n")
343             .append("}\n");
344     }
345 
346     // Appends ftrace-related data sources to buffer 0 (linux.ftrace, android.gpu.memory).
appendFtraceConfig(StringBuilder config, Collection<String> tags, boolean apps)347     private void appendFtraceConfig(StringBuilder config, Collection<String> tags, boolean apps) {
348         config.append("data_sources {\n")
349             .append("  config {\n")
350             .append("    name: \"linux.ftrace\"\n")
351             .append("    target_buffer: 0\n")
352             .append("    ftrace_config {\n")
353             .append("      symbolize_ksyms: true\n");
354 
355         for (String tag : tags) {
356             // Tags are expected to be only letters, numbers, and underscores.
357             String cleanTag = tag.replaceAll("[^a-zA-Z0-9_]", "");
358             if (!cleanTag.equals(tag)) {
359                 Log.w(TAG, "Attempting to use an invalid tag: " + tag);
360             }
361             config.append("      atrace_categories: \"" + cleanTag + "\"\n");
362         }
363 
364         if (apps) {
365             config.append("      atrace_apps: \"*\"\n");
366         }
367 
368         // Request a dense encoding of the common sched events (sched_switch, sched_waking).
369         if (tags.contains(SCHED_TAG)) {
370             config.append("      compact_sched {\n");
371             config.append("        enabled: true\n");
372             config.append("      }\n");
373         }
374 
375         // These parameters affect only the kernel trace buffer size and how
376         // frequently it gets moved into the userspace buffer defined above.
377         config.append("      buffer_size_kb: 8192\n")
378             .append("    }\n")
379             .append("  }\n")
380             .append("}\n")
381             .append("\n");
382 
383         // Captures initial counter values, updates are captured in ftrace.
384         if (tags.contains(MEMORY_TAG) || tags.contains(GFX_TAG)) {
385              config.append("data_sources: {\n")
386                 .append("  config { \n")
387                 .append("    name: \"android.gpu.memory\"\n")
388                 .append("    target_buffer: 0\n")
389                 .append("  }\n")
390                 .append("}\n");
391         }
392     }
393 
394     // Appends the linux.process_stats data source to the specified target buffer.
appendProcStatsConfig(StringBuilder config, Collection<String> tags, int targetBuffer)395     private void appendProcStatsConfig(StringBuilder config, Collection<String> tags,
396             int targetBuffer) {
397         boolean tagsContainsMemory = (tags != null) ? tags.contains(MEMORY_TAG) : false;
398         // For process association. If the memory tag is enabled, poll periodically instead of just
399         // once at the beginning.
400         config.append("data_sources {\n")
401             .append("  config {\n")
402             .append("    name: \"linux.process_stats\"\n")
403             .append("    target_buffer: " + targetBuffer + "\n")
404             .append("    process_stats_config {\n");
405         if (tagsContainsMemory) {
406             config.append("      proc_stats_poll_ms: 60000\n");
407         } else {
408             config.append("      scan_all_processes_on_start: true\n");
409         }
410         config.append("    }\n")
411             .append("  }\n")
412             .append("}\n");
413     }
414 
415     // Appends the callstack-sampling data source. Sampling frequency is measured in Hz.
appendLinuxPerfConfig(StringBuilder config, int targetBuffer)416     private void appendLinuxPerfConfig(StringBuilder config, int targetBuffer) {
417         config.append("data_sources: {\n")
418             .append("  config {\n")
419             .append("    name: \"linux.perf\"\n")
420             .append("    target_buffer: " + targetBuffer + "\n")
421             .append("    perf_event_config {\n")
422             .append("      all_cpus: true\n")
423             .append("      sampling_frequency: 100\n")
424             .append("    }\n")
425             .append("  }\n")
426             .append("}\n");
427     }
428 
429     // Appends additional data sources to the specified extra buffer based on enabled trace tags.
appendAdditionalDataSources(StringBuilder config, Collection<String> tags, boolean longTrace, int targetBuffer)430     private void appendAdditionalDataSources(StringBuilder config, Collection<String> tags,
431             boolean longTrace, int targetBuffer) {
432         if (tags.contains(POWER_TAG)) {
433             config.append("data_sources: {\n")
434                 .append("  config { \n")
435                 .append("    name: \"android.power\"\n")
436                 .append("    target_buffer: " + targetBuffer + "\n")
437                 .append("    android_power_config {\n");
438             if (longTrace) {
439                 config.append("      battery_poll_ms: 5000\n");
440             } else {
441                 config.append("      battery_poll_ms: 1000\n");
442             }
443             config.append("      collect_power_rails: true\n")
444                 .append("      battery_counters: BATTERY_COUNTER_CAPACITY_PERCENT\n")
445                 .append("      battery_counters: BATTERY_COUNTER_CHARGE\n")
446                 .append("      battery_counters: BATTERY_COUNTER_CURRENT\n")
447                 .append("    }\n")
448                 .append("  }\n")
449                 .append("}\n");
450         }
451 
452         if (tags.contains(SYS_STATS_TAG)) {
453             config.append("data_sources: {\n")
454                 .append("  config { \n")
455                 .append("    name: \"linux.sys_stats\"\n")
456                 .append("    target_buffer: " + targetBuffer + "\n")
457                 .append("    sys_stats_config {\n")
458                 .append("      meminfo_period_ms: 1000\n")
459                 .append("      vmstat_period_ms: 1000\n")
460                 .append("    }\n")
461                 .append("  }\n")
462                 .append("}\n");
463         }
464 
465         if (tags.contains(LOG_TAG)) {
466             config.append("data_sources: {\n")
467                 .append("  config {\n")
468                 .append("    name: \"android.log\"\n")
469                 .append("    target_buffer: " + targetBuffer + "\n")
470                 .append("  }\n")
471                 .append("}\n");
472         }
473 
474         if (tags.contains(CPU_TAG)) {
475             appendLinuxPerfConfig(config, /* target_buffer = */ 1);
476         }
477 
478         if (tags.contains(GFX_TAG)) {
479             config.append("data_sources: {\n")
480                 .append("  config { \n")
481                 .append("    name: \"android.surfaceflinger.frametimeline\"\n")
482                 .append("    target_buffer: " + targetBuffer + "\n")
483                 .append("  }\n")
484                 .append("}\n");
485         }
486 
487         if (tags.contains(CAMERA_TAG)) {
488             config.append("data_sources: {\n")
489                 .append("  config { \n")
490                 .append("    name: \"android.hardware.camera\"\n")
491                 .append("    target_buffer: " + targetBuffer + "\n")
492                 .append("  }\n")
493                 .append("}\n");
494         }
495 
496         if (tags.contains(NETWORK_TAG)) {
497             config.append("data_sources: {\n")
498                 .append("  config { \n")
499                 .append("    name: \"android.network_packets\"\n")
500                 .append("    target_buffer: " + targetBuffer + "\n")
501                 .append("    network_packet_trace_config {\n")
502                 .append("      poll_ms: 250\n")
503                 .append("    }\n")
504                 .append("  }\n")
505                 .append("}\n");
506             // Include the packages_list data source so that we can map UIDs
507             // from Network Tracing to the corresponding package name.
508             config.append("data_sources: {\n")
509                 .append("  config { \n")
510                 .append("    name: \"android.packages_list\"\n")
511                 .append("    target_buffer: " + targetBuffer + "\n")
512                 .append("  }\n")
513                 .append("}\n");
514         }
515 
516         // Also enable Chrome events when the WebView tag is enabled.
517         if (tags.contains(WEBVIEW_TAG)) {
518             String chromeTraceConfig =  "{" +
519                 "\\\"record_mode\\\":\\\"record-continuously\\\"," +
520                 "\\\"included_categories\\\":[\\\"*\\\"]" +
521                 "}";
522             config.append("data_sources: {\n")
523                 .append("  config {\n")
524                 .append("    name: \"org.chromium.trace_event\"\n")
525                 .append("    target_buffer: " + targetBuffer + "\n")
526                 .append("    chrome_config {\n")
527                 .append("      trace_config: \"" + chromeTraceConfig + "\"\n")
528                 .append("    }\n")
529                 .append("  }\n")
530                 .append("}\n")
531                 .append("data_sources: {\n")
532                 .append("  config {\n")
533                 .append("    name: \"org.chromium.trace_metadata\"\n")
534                 .append("    target_buffer: " + targetBuffer + "\n")
535                 .append("    chrome_config {\n")
536                 .append("      trace_config: \"" + chromeTraceConfig + "\"\n")
537                 .append("    }\n")
538                 .append("  }\n")
539                 .append("}\n");
540         }
541     }
542 }
543