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