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