• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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