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