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.os.Build; 20 import android.os.AsyncTask; 21 import android.os.FileUtils; 22 import android.util.Log; 23 24 import java.io.BufferedReader; 25 import java.io.File; 26 import java.io.InputStream; 27 import java.io.InputStreamReader; 28 import java.io.IOException; 29 import java.io.OutputStream; 30 import java.text.SimpleDateFormat; 31 import java.util.Arrays; 32 import java.util.Date; 33 import java.util.Locale; 34 import java.util.Collection; 35 import java.util.concurrent.TimeUnit; 36 import java.util.TreeMap; 37 38 /** 39 * Utility functions for tracing. 40 * Will call atrace or perfetto depending on the setting. 41 */ 42 public class TraceUtils { 43 44 static final String TAG = "Traceur"; 45 46 public static final String TRACE_DIRECTORY = "/data/local/traces/"; 47 48 // To change Traceur to use atrace to collect traces, 49 // change mTraceEngine to point to AtraceUtils(). 50 private static TraceEngine mTraceEngine = new PerfettoUtils(); 51 52 private static final Runtime RUNTIME = Runtime.getRuntime(); 53 private static final int PROCESS_TIMEOUT_MS = 30000; // 30 seconds 54 55 public interface TraceEngine { getName()56 public String getName(); getOutputExtension()57 public String getOutputExtension(); traceStart(Collection<String> tags, int bufferSizeKb, boolean apps, boolean attachToBugreport, boolean longTrace, int maxLongTraceSizeMb, int maxLongTraceDurationMinutes)58 public boolean traceStart(Collection<String> tags, int bufferSizeKb, boolean apps, 59 boolean attachToBugreport, boolean longTrace, int maxLongTraceSizeMb, 60 int maxLongTraceDurationMinutes); traceStop()61 public void traceStop(); traceDump(File outFile)62 public boolean traceDump(File outFile); isTracingOn()63 public boolean isTracingOn(); 64 } 65 currentTraceEngine()66 public static String currentTraceEngine() { 67 return mTraceEngine.getName(); 68 } 69 traceStart(Collection<String> tags, int bufferSizeKb, boolean apps, boolean longTrace, boolean attachToBugreport, int maxLongTraceSizeMb, int maxLongTraceDurationMinutes)70 public static boolean traceStart(Collection<String> tags, int bufferSizeKb, boolean apps, 71 boolean longTrace, boolean attachToBugreport, int maxLongTraceSizeMb, 72 int maxLongTraceDurationMinutes) { 73 return mTraceEngine.traceStart(tags, bufferSizeKb, apps, 74 attachToBugreport, longTrace, maxLongTraceSizeMb, maxLongTraceDurationMinutes); 75 } 76 traceStop()77 public static void traceStop() { 78 mTraceEngine.traceStop(); 79 } 80 traceDump(File outFile)81 public static boolean traceDump(File outFile) { 82 return mTraceEngine.traceDump(outFile); 83 } 84 isTracingOn()85 public static boolean isTracingOn() { 86 return mTraceEngine.isTracingOn(); 87 } 88 listCategories()89 public static TreeMap<String, String> listCategories() { 90 return PerfettoUtils.perfettoListCategories(); 91 } 92 clearSavedTraces()93 public static void clearSavedTraces() { 94 String cmd = "rm -f " + TRACE_DIRECTORY + "trace-*.*trace"; 95 96 Log.v(TAG, "Clearing trace directory: " + cmd); 97 try { 98 Process rm = exec(cmd); 99 100 if (rm.waitFor() != 0) { 101 Log.e(TAG, "clearSavedTraces failed with: " + rm.exitValue()); 102 } 103 } catch (Exception e) { 104 throw new RuntimeException(e); 105 } 106 } 107 exec(String cmd)108 public static Process exec(String cmd) throws IOException { 109 return exec(cmd, null); 110 } 111 exec(String cmd, String tmpdir)112 public static Process exec(String cmd, String tmpdir) throws IOException { 113 return exec(cmd, tmpdir, true); 114 } 115 exec(String cmd, String tmpdir, boolean logOutput)116 public static Process exec(String cmd, String tmpdir, boolean logOutput) throws IOException { 117 String[] cmdarray = {"sh", "-c", cmd}; 118 String[] envp = {"TMPDIR=" + tmpdir}; 119 envp = tmpdir == null ? null : envp; 120 121 Log.v(TAG, "exec: " + Arrays.toString(envp) + " " + Arrays.toString(cmdarray)); 122 123 Process process = RUNTIME.exec(cmdarray, envp); 124 new Logger("traceService:stderr", process.getErrorStream()); 125 if (logOutput) { 126 new Logger("traceService:stdout", process.getInputStream()); 127 } 128 129 return process; 130 } 131 132 // Returns the Process if the command terminated on time and null if not. execWithTimeout(String cmd, String tmpdir, long timeout)133 public static Process execWithTimeout(String cmd, String tmpdir, long timeout) 134 throws IOException { 135 Process process = exec(cmd, tmpdir, true); 136 try { 137 if (!process.waitFor(timeout, TimeUnit.MILLISECONDS)) { 138 Log.e(TAG, "Command '" + cmd + "' has timed out after " + timeout + " ms."); 139 process.destroyForcibly(); 140 // Return null to signal a timeout and that the Process was destroyed. 141 return null; 142 } 143 } catch (Exception e) { 144 throw new RuntimeException(e); 145 } 146 return process; 147 } 148 getOutputFilename()149 public static String getOutputFilename() { 150 String format = "yyyy-MM-dd-HH-mm-ss"; 151 String now = new SimpleDateFormat(format, Locale.US).format(new Date()); 152 return String.format("trace-%s-%s-%s.%s", Build.BOARD, Build.ID, now, 153 mTraceEngine.getOutputExtension()); 154 } 155 getOutputFile(String filename)156 public static File getOutputFile(String filename) { 157 return new File(TraceUtils.TRACE_DIRECTORY, filename); 158 } 159 cleanupOlderFiles(final int minCount, final long minAge)160 protected static void cleanupOlderFiles(final int minCount, final long minAge) { 161 new AsyncTask<Void, Void, Void>() { 162 @Override 163 protected Void doInBackground(Void... params) { 164 try { 165 FileUtils.deleteOlderFiles(new File(TRACE_DIRECTORY), minCount, minAge); 166 } catch (RuntimeException e) { 167 Log.e(TAG, "Failed to delete older traces", e); 168 } 169 return null; 170 } 171 }.execute(); 172 } 173 174 /** 175 * Streams data from an InputStream to an OutputStream 176 */ 177 static class Streamer { 178 private boolean mDone; 179 Streamer(final String tag, final InputStream in, final OutputStream out)180 Streamer(final String tag, final InputStream in, final OutputStream out) { 181 new Thread(tag) { 182 @Override 183 public void run() { 184 int read; 185 byte[] buf = new byte[2 << 10]; 186 try { 187 while ((read = in.read(buf)) != -1) { 188 out.write(buf, 0, read); 189 } 190 } catch (IOException e) { 191 Log.e(TAG, "Error while streaming " + tag); 192 } finally { 193 try { 194 out.close(); 195 } catch (IOException e) { 196 // Welp. 197 } 198 synchronized (Streamer.this) { 199 mDone = true; 200 Streamer.this.notify(); 201 } 202 } 203 } 204 }.start(); 205 } 206 isDone()207 synchronized boolean isDone() { 208 return mDone; 209 } 210 waitForDone()211 synchronized void waitForDone() { 212 while (!isDone()) { 213 try { 214 wait(); 215 } catch (InterruptedException e) { 216 Thread.currentThread().interrupt(); 217 } 218 } 219 } 220 } 221 222 /** 223 * Redirects an InputStream to logcat. 224 */ 225 private static class Logger { 226 Logger(final String tag, final InputStream in)227 Logger(final String tag, final InputStream in) { 228 new Thread(tag) { 229 @Override 230 public void run() { 231 String line; 232 BufferedReader r = new BufferedReader(new InputStreamReader(in)); 233 try { 234 while ((line = r.readLine()) != null) { 235 Log.e(TAG, tag + ": " + line); 236 } 237 } catch (IOException e) { 238 Log.e(TAG, "Error while streaming " + tag); 239 } finally { 240 try { 241 r.close(); 242 } catch (IOException e) { 243 // Welp. 244 } 245 } 246 } 247 }.start(); 248 } 249 } 250 } 251