1 /* 2 * Copyright (C) 2018 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.helpers; 18 19 import android.os.SystemClock; 20 import android.support.test.uiautomator.UiDevice; 21 import android.util.Log; 22 import androidx.test.InstrumentationRegistry; 23 24 import java.io.IOException; 25 import java.io.File; 26 import java.nio.file.Path; 27 import java.nio.file.Paths; 28 29 /** 30 * PerfettoHelper is used to start and stop the perfetto tracing and move the 31 * output perfetto trace file to destination folder. 32 */ 33 public class PerfettoHelper { 34 35 private static final String LOG_TAG = PerfettoHelper.class.getSimpleName(); 36 private static final String PERFETTO_ROOT_DIR = "/data/misc/perfetto-traces/"; 37 // Command to start the perfetto tracing in the background. 38 // perfetto -b -c /data/misc/perfetto-traces/trace_config.pb -o 39 // /data/misc/perfetto-traces/trace_output.pb 40 private static final String PERFETTO_START_CMD = "perfetto --background -c %s%s -o %s"; 41 private static final String PERFETTO_TMP_OUTPUT_FILE = 42 "/data/misc/perfetto-traces/trace_output.pb"; 43 // Additional arg to indicate that the perfetto config file is text format. 44 private static final String PERFETTO_TXT_PROTO_ARG = " --txt"; 45 // Command to stop (i.e kill) the perfetto tracing. 46 private static final String PERFETTO_STOP_CMD = "pkill -INT perfetto"; 47 // Command to check the perfetto process id. 48 private static final String PERFETTO_PROC_ID_CMD = "pidof perfetto"; 49 // Remove the trace output file /data/misc/perfetto-traces/trace_output.pb 50 private static final String REMOVE_CMD = "rm %s"; 51 // Command to move the perfetto output trace file to given folder. 52 private static final String MOVE_CMD = "mv %s %s"; 53 // Max wait count for checking if perfetto is stopped successfully 54 private static final int PERFETTO_KILL_WAIT_COUNT = 12; 55 // Check if perfetto is stopped every 5 secs. 56 private static final long PERFETTO_KILL_WAIT_TIME = 5000; 57 58 private UiDevice mUIDevice; 59 60 /** 61 * Start the perfetto tracing in background using the given config file and write the ouput to 62 * /data/misc/perfetto-traces/trace_output.pb. Perfetto has access only to 63 * /data/misc/perfetto-traces/ folder. So the config file has to be under 64 * /data/misc/perfetto-traces/ folder in the device. 65 * 66 * @param configFileName used for collecting the perfetto trace. 67 * @param isTextProtoConfig true if the config file is textproto format otherwise false. 68 * @return true if trace collection started successfully otherwise return false. 69 */ startCollecting(String configFileName, boolean isTextProtoConfig)70 public boolean startCollecting(String configFileName, boolean isTextProtoConfig) { 71 mUIDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); 72 if (configFileName == null || configFileName.isEmpty()) { 73 Log.e(LOG_TAG, "Perfetto config file name is null or empty."); 74 return false; 75 } 76 try { 77 // Cleanup already existing perfetto process. 78 Log.i(LOG_TAG, "Cleanup perfetto before starting."); 79 if (isPerfettoRunning()) { 80 Log.i(LOG_TAG, "Perfetto tracing is already running. Stopping perfetto."); 81 if (!stopPerfetto()) { 82 return false; 83 } 84 } 85 86 // Remove already existing temporary output trace file if any. 87 String output = mUIDevice.executeShellCommand(String.format(REMOVE_CMD, 88 PERFETTO_TMP_OUTPUT_FILE)); 89 Log.i(LOG_TAG, String.format("Perfetto output file cleanup - %s", output)); 90 91 String perfettoCmd = String.format(PERFETTO_START_CMD, 92 PERFETTO_ROOT_DIR, configFileName, PERFETTO_TMP_OUTPUT_FILE); 93 94 if(isTextProtoConfig) { 95 perfettoCmd = perfettoCmd + PERFETTO_TXT_PROTO_ARG; 96 } 97 98 // Start perfetto tracing. 99 Log.i(LOG_TAG, "Starting perfetto tracing."); 100 String startOutput = mUIDevice.executeShellCommand(perfettoCmd); 101 Log.i(LOG_TAG, String.format("Perfetto start command output - %s", startOutput)); 102 // TODO : Once the output status is available use that for additional validation. 103 if (!isPerfettoRunning()) { 104 Log.e(LOG_TAG, "Perfetto tracing failed to start."); 105 return false; 106 } 107 } catch (IOException ioe) { 108 Log.e(LOG_TAG, "Unable to start the perfetto tracing due to :" + ioe.getMessage()); 109 return false; 110 } 111 Log.i(LOG_TAG, "Perfetto tracing started successfully."); 112 return true; 113 } 114 115 /** 116 * Stop the perfetto trace collection under /data/misc/perfetto-traces/trace_output.pb after 117 * waiting for given time in msecs and copy the output to the destination file. 118 * 119 * @param waitTimeInMsecs time to wait in msecs before stopping the trace collection. 120 * @param destinationFile file to copy the perfetto output trace. 121 * @return true if the trace collection is successfull otherwise false. 122 */ stopCollecting(long waitTimeInMsecs, String destinationFile)123 public boolean stopCollecting(long waitTimeInMsecs, String destinationFile) { 124 // Wait for the dump interval before stopping the trace. 125 Log.i(LOG_TAG, String.format( 126 "Waiting for %d msecs before stopping perfetto.", waitTimeInMsecs)); 127 SystemClock.sleep(waitTimeInMsecs); 128 129 // Stop the perfetto and copy the output file. 130 Log.i(LOG_TAG, "Stopping perfetto."); 131 try { 132 if (stopPerfetto()) { 133 if (!copyFileOutput(destinationFile)) { 134 return false; 135 } 136 } else { 137 Log.e(LOG_TAG, "Perfetto failed to stop."); 138 return false; 139 } 140 } catch (IOException ioe) { 141 Log.e(LOG_TAG, "Unable to stop the perfetto tracing due to " + ioe.getMessage()); 142 return false; 143 } 144 return true; 145 } 146 147 /** 148 * Utility method for stopping perfetto. 149 * 150 * @return true if perfetto is stopped successfully. 151 */ stopPerfetto()152 public boolean stopPerfetto() throws IOException { 153 String stopOutput = mUIDevice.executeShellCommand(PERFETTO_STOP_CMD); 154 Log.i(LOG_TAG, String.format("Perfetto stop command output - %s", stopOutput)); 155 int waitCount = 0; 156 while (isPerfettoRunning()) { 157 // 60 secs timeout for perfetto shutdown. 158 if (waitCount < PERFETTO_KILL_WAIT_COUNT) { 159 // Check every 5 secs if perfetto stopped successfully. 160 SystemClock.sleep(PERFETTO_KILL_WAIT_TIME); 161 waitCount++; 162 continue; 163 } 164 return false; 165 } 166 Log.e(LOG_TAG, "Perfetto stopped successfully."); 167 return true; 168 } 169 170 /** 171 * Check if perfetto process is running or not. 172 * 173 * @return true if perfetto is running otherwise false. 174 */ isPerfettoRunning()175 private boolean isPerfettoRunning() { 176 try { 177 String perfettoProcId = mUIDevice.executeShellCommand(PERFETTO_PROC_ID_CMD); 178 Log.i(LOG_TAG, String.format("Perfetto process id - %s", perfettoProcId)); 179 if (perfettoProcId.isEmpty()) { 180 return false; 181 } 182 } catch (IOException ioe) { 183 Log.e(LOG_TAG, "Not able to check the perfetto status due to:" + ioe.getMessage()); 184 return false; 185 } 186 return true; 187 } 188 189 /** 190 * Copy the temporary perfetto trace output file from /data/misc/perfetto-traces/ to given 191 * destinationFile. 192 * 193 * @param destinationFile file to copy the perfetto output trace. 194 * @return true if the trace file copied successfully otherwise false. 195 */ copyFileOutput(String destinationFile)196 private boolean copyFileOutput(String destinationFile) { 197 Path path = Paths.get(destinationFile); 198 String destDirectory = path.getParent().toString(); 199 // Check if the directory already exists 200 File directory = new File(destDirectory); 201 if (!directory.exists()) { 202 boolean success = directory.mkdirs(); 203 if (!success) { 204 Log.e(LOG_TAG, String.format( 205 "Result output directory %s not created successfully.", destDirectory)); 206 return false; 207 } 208 } 209 210 // Copy the collected trace from /data/misc/perfetto-traces/trace_output.pb to 211 // destinationFile 212 try { 213 String moveResult = mUIDevice.executeShellCommand(String.format( 214 MOVE_CMD, PERFETTO_TMP_OUTPUT_FILE, destinationFile)); 215 if (!moveResult.isEmpty()) { 216 Log.e(LOG_TAG, String.format( 217 "Unable to move perfetto output file from %s to %s due to %s", 218 PERFETTO_TMP_OUTPUT_FILE, destinationFile, moveResult)); 219 return false; 220 } 221 } catch (IOException ioe) { 222 Log.e(LOG_TAG, 223 "Unable to move the perfetto trace file to destination file." 224 + ioe.getMessage()); 225 return false; 226 } 227 return true; 228 } 229 } 230