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