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