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.io.IOException; 24 import java.nio.file.Files; 25 import java.nio.file.Path; 26 import java.nio.file.Paths; 27 import java.util.Collection; 28 import java.util.concurrent.TimeUnit; 29 30 /** 31 * Utility functions for calling Perfetto 32 */ 33 public class PerfettoUtils implements TraceUtils.TraceEngine { 34 35 static final String TAG = "Traceur"; 36 public static final String NAME = "PERFETTO"; 37 38 private static final String OUTPUT_EXTENSION = "perfetto-trace"; 39 private static final String TEMP_DIR= "/data/local/traces/"; 40 private static final String TEMP_TRACE_LOCATION = "/data/local/traces/.trace-in-progress.trace"; 41 42 private static final String PERFETTO_TAG = "traceur"; 43 private static final String MARKER = "PERFETTO_ARGUMENTS"; 44 private static final int STARTUP_TIMEOUT_MS = 10000; 45 private static final long MEGABYTES_TO_BYTES = 1024L * 1024L; 46 private static final long MINUTES_TO_MILLISECONDS = 60L * 1000L; 47 48 private static final String GFX_TAG = "gfx"; 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 attachToBugreport, boolean longTrace, int maxLongTraceSizeMb, int maxLongTraceDurationMinutes)61 public boolean traceStart(Collection<String> tags, int bufferSizeKb, boolean apps, 62 boolean attachToBugreport, boolean longTrace, int maxLongTraceSizeMb, 63 int maxLongTraceDurationMinutes) { 64 if (isTracingOn()) { 65 Log.e(TAG, "Attempting to start perfetto trace but trace is already in progress"); 66 return false; 67 } else { 68 // Ensure the temporary trace file is cleared. 69 try { 70 Files.deleteIfExists(Paths.get(TEMP_TRACE_LOCATION)); 71 } catch (Exception e) { 72 throw new RuntimeException(e); 73 } 74 } 75 76 // The user chooses a per-CPU buffer size due to atrace limitations. 77 // So we use this to ensure that we reserve the correctly-sized buffer. 78 int numCpus = Runtime.getRuntime().availableProcessors(); 79 80 // Build the perfetto config that will be passed on the command line. 81 StringBuilder config = new StringBuilder() 82 .append("write_into_file: true\n") 83 // Ensure that we flush ftrace data every 30s even if cpus are idle. 84 .append("flush_period_ms: 30000\n"); 85 86 // If the user has flagged that in-progress trace sessions should be grabbed 87 // during bugreports, and BetterBug is present. 88 if (attachToBugreport) { 89 config.append("bugreport_score: 500\n"); 90 } 91 92 // Indicates that perfetto should notify Traceur if the tracing session's status 93 // changes. 94 config.append("notify_traceur: true\n"); 95 96 if (longTrace) { 97 if (maxLongTraceSizeMb != 0) { 98 config.append("max_file_size_bytes: " 99 + (maxLongTraceSizeMb * MEGABYTES_TO_BYTES) + "\n"); 100 } 101 102 if (maxLongTraceDurationMinutes != 0) { 103 config.append("duration_ms: " 104 + (maxLongTraceDurationMinutes * MINUTES_TO_MILLISECONDS) 105 + "\n"); 106 } 107 108 // Default value for long traces to write to file. 109 config.append("file_write_period_ms: 1000\n"); 110 } else { 111 // For short traces, we don't write to the file. 112 // So, always use the maximum value here: 7 days. 113 config.append("file_write_period_ms: 604800000\n"); 114 } 115 116 config.append("incremental_state_config {\n") 117 .append(" clear_period_ms: 15000\n") 118 .append("} \n") 119 // This is target_buffer: 0, which is used for ftrace and the ftrace-derived 120 // android.gpu.memory. 121 .append("buffers {\n") 122 .append(" size_kb: " + bufferSizeKb * numCpus + "\n") 123 .append(" fill_policy: RING_BUFFER\n") 124 .append("} \n") 125 // This is target_buffer: 1, which is used for additional data sources. 126 .append("buffers {\n") 127 .append(" size_kb: 2048\n") 128 .append(" fill_policy: RING_BUFFER\n") 129 .append("} \n") 130 .append("data_sources {\n") 131 .append(" config {\n") 132 .append(" name: \"linux.ftrace\"\n") 133 .append(" target_buffer: 0\n") 134 .append(" ftrace_config {\n") 135 .append(" symbolize_ksyms: true\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 // Request a dense encoding of the common sched events (sched_switch, sched_waking). 151 if (tags.contains(SCHED_TAG)) { 152 config.append(" compact_sched {\n"); 153 config.append(" enabled: true\n"); 154 config.append(" }\n"); 155 } 156 157 // These parameters affect only the kernel trace buffer size and how 158 // frequently it gets moved into the userspace buffer defined above. 159 config.append(" buffer_size_kb: 8192\n") 160 .append(" drain_period_ms: 1000\n") 161 .append(" }\n") 162 .append(" }\n") 163 .append("}\n") 164 .append(" \n"); 165 166 // Captures initial counter values, updates are captured in ftrace. 167 if (tags.contains(MEMORY_TAG) || tags.contains(GFX_TAG)) { 168 config.append("data_sources: {\n") 169 .append(" config { \n") 170 .append(" name: \"android.gpu.memory\"\n") 171 .append(" target_buffer: 0\n") 172 .append(" }\n") 173 .append("}\n"); 174 } 175 176 // For process association. If the memory tag is enabled, 177 // poll periodically instead of just once at the beginning. 178 config.append("data_sources {\n") 179 .append(" config {\n") 180 .append(" name: \"linux.process_stats\"\n") 181 .append(" target_buffer: 1\n"); 182 if (tags.contains(MEMORY_TAG)) { 183 config.append(" process_stats_config {\n") 184 .append(" proc_stats_poll_ms: 60000\n") 185 .append(" }\n"); 186 } 187 config.append(" }\n") 188 .append("} \n"); 189 190 if (tags.contains(POWER_TAG)) { 191 config.append("data_sources: {\n") 192 .append(" config { \n") 193 .append(" name: \"android.power\"\n") 194 .append(" target_buffer: 1\n") 195 .append(" android_power_config {\n"); 196 if (longTrace) { 197 config.append(" battery_poll_ms: 5000\n"); 198 } else { 199 config.append(" battery_poll_ms: 1000\n"); 200 } 201 config.append(" collect_power_rails: true\n") 202 .append(" battery_counters: BATTERY_COUNTER_CAPACITY_PERCENT\n") 203 .append(" battery_counters: BATTERY_COUNTER_CHARGE\n") 204 .append(" battery_counters: BATTERY_COUNTER_CURRENT\n") 205 .append(" }\n") 206 .append(" }\n") 207 .append("}\n"); 208 } 209 210 if (tags.contains(MEMORY_TAG)) { 211 config.append("data_sources: {\n") 212 .append(" config { \n") 213 .append(" name: \"android.sys_stats\"\n") 214 .append(" target_buffer: 1\n") 215 .append(" sys_stats_config {\n") 216 .append(" vmstat_period_ms: 1000\n") 217 .append(" }\n") 218 .append(" }\n") 219 .append("}\n"); 220 } 221 222 if (tags.contains(GFX_TAG)) { 223 config.append("data_sources: {\n") 224 .append(" config { \n") 225 .append(" name: \"android.surfaceflinger.frametimeline\"\n") 226 .append(" }\n") 227 .append("}\n"); 228 } 229 230 String configString = config.toString(); 231 232 // If the here-doc ends early, within the config string, exit immediately. 233 // This should never happen. 234 if (configString.contains(MARKER)) { 235 throw new RuntimeException("The arguments to the Perfetto command are malformed."); 236 } 237 238 String cmd = "perfetto --detach=" + PERFETTO_TAG 239 + " -o " + TEMP_TRACE_LOCATION 240 + " -c - --txt" 241 + " <<" + MARKER +"\n" + configString + "\n" + MARKER; 242 243 Log.v(TAG, "Starting perfetto trace."); 244 try { 245 Process process = TraceUtils.exec(cmd, TEMP_DIR); 246 247 // If we time out, ensure that the perfetto process is destroyed. 248 if (!process.waitFor(STARTUP_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { 249 Log.e(TAG, "perfetto traceStart has timed out after " 250 + STARTUP_TIMEOUT_MS + " ms."); 251 process.destroyForcibly(); 252 return false; 253 } 254 255 if (process.exitValue() != 0) { 256 Log.e(TAG, "perfetto traceStart failed with: " 257 + process.exitValue()); 258 return false; 259 } 260 } catch (Exception e) { 261 throw new RuntimeException(e); 262 } 263 264 Log.v(TAG, "perfetto traceStart succeeded!"); 265 return true; 266 } 267 traceStop()268 public void traceStop() { 269 Log.v(TAG, "Stopping perfetto trace."); 270 271 if (!isTracingOn()) { 272 Log.w(TAG, "No trace appears to be in progress. Stopping perfetto trace may not work."); 273 } 274 275 String cmd = "perfetto --stop --attach=" + PERFETTO_TAG; 276 try { 277 Process process = TraceUtils.exec(cmd); 278 if (process.waitFor() != 0) { 279 Log.e(TAG, "perfetto traceStop failed with: " + process.exitValue()); 280 } 281 } catch (Exception e) { 282 throw new RuntimeException(e); 283 } 284 } 285 traceDump(File outFile)286 public boolean traceDump(File outFile) { 287 traceStop(); 288 289 // Short-circuit if the file we're trying to dump to doesn't exist. 290 if (!Files.exists(Paths.get(TEMP_TRACE_LOCATION))) { 291 Log.e(TAG, "In-progress trace file doesn't exist, aborting trace dump."); 292 return false; 293 } 294 295 Log.v(TAG, "Saving perfetto trace to " + outFile); 296 297 try { 298 Os.rename(TEMP_TRACE_LOCATION, outFile.getCanonicalPath()); 299 } catch (Exception e) { 300 throw new RuntimeException(e); 301 } 302 303 outFile.setReadable(true, false); // (readable, ownerOnly) 304 outFile.setWritable(true, false); // (readable, ownerOnly) 305 return true; 306 } 307 isTracingOn()308 public boolean isTracingOn() { 309 String cmd = "perfetto --is_detached=" + PERFETTO_TAG; 310 311 try { 312 Process process = TraceUtils.exec(cmd); 313 314 // 0 represents a detached process exists with this name 315 // 2 represents no detached process with this name 316 // 1 (or other error code) represents an error 317 int result = process.waitFor(); 318 if (result == 0) { 319 return true; 320 } else if (result == 2) { 321 return false; 322 } else { 323 throw new RuntimeException("Perfetto error: " + result); 324 } 325 } catch (Exception e) { 326 throw new RuntimeException(e); 327 } 328 } 329 } 330