• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.support.test.aupt;
18 
19 import android.app.UiAutomation;
20 import android.os.Bundle;
21 import android.os.ParcelFileDescriptor;
22 import android.util.Log;
23 
24 import java.io.BufferedReader;
25 import java.io.InputStreamReader;
26 import java.io.IOException;
27 import java.util.ArrayList;
28 import java.util.HashMap;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Timer;
32 import java.util.TimerTask;
33 
34 /**
35  * GraphicsStatsMonitor is an internal monitor for AUPT to poll and track the information coming out
36  * of the shell command, "dumpsys graphicsstats." In particular, the purpose of this is to see jank
37  * statistics across the lengthy duration of an AUPT run.
38  * <p>
39  * To use the monitor, simply specify the options: trackJank true and jankInterval n, where n is
40  * the polling interval in milliseconds. The default is 5 minutes. Also, it should be noted that
41  * the trackJank option is unnecessary and this comment should be removed at the same time as it.
42  * <p>
43  * This graphics service monitors jank levels grouped by foreground process. Even when the process
44  * is killed, the monitor will continue to track information, unless the buffer runs out of space.
45  * This should only occur when too many foreground processes have been killed and the service
46  * decides to clear itself. When pulling the information out of the monitor, these separate images
47  * are combined to provide a single image as output. The linear information is preserved by simply
48  * adding the values together. However, certain information such as the jank percentiles are
49  * approximated using a weighted average.
50  */
51 public class GraphicsStatsMonitor {
52     private static final String TAG = "GraphicsStatsMonitor";
53 
54     public static final int MS_IN_SECS = 1000;
55     public static final int SECS_IN_MIN = 60;
56     public static final long DEFAULT_INTERVAL_RATE = 5 * SECS_IN_MIN * MS_IN_SECS;
57 
58     private Timer mIntervalTimer;
59     private TimerTask mIntervalTask;
60     private long mIntervalRate;
61     private boolean mIsRunning;
62 
63     private Map<String, List<JankStat>> mGraphicsStatsRecords;
64 
65 
GraphicsStatsMonitor()66     public GraphicsStatsMonitor () {
67         mIntervalTask = new TimerTask() {
68             @Override
69             public void run () {
70                 if (mIsRunning) {
71                     grabStatsImage();
72                 }
73             }
74         };
75         mIntervalRate = DEFAULT_INTERVAL_RATE;
76         mIsRunning = false;
77     }
78 
79     /**
80      * Sets the monitoring interval rate if the monitor isn't currently running
81      */
setIntervalRate(long intervalRate)82     public void setIntervalRate (long intervalRate) {
83         if (mIsRunning) {
84             Log.e(TAG, "Can't set interval rate for monitor that is already running");
85         } else {
86             mIntervalRate = intervalRate;
87             Log.v(TAG, String.format("Set jank monitor interval rate to %d", intervalRate));
88         }
89     }
90 
91     /**
92      * Starts to monitor graphics stats on the interval timer after clearing the stats
93      */
startMonitoring()94     public void startMonitoring () {
95         if (mGraphicsStatsRecords == null) {
96             mGraphicsStatsRecords = new HashMap<>();
97         }
98 
99         clearGraphicsStats();
100 
101         // Schedule a daemon timer to grab stats periodically
102         mIntervalTimer = new Timer(true);
103         mIntervalTimer.schedule(mIntervalTask, 0, mIntervalRate);
104 
105         mIsRunning = true;
106         Log.d(TAG, "Started monitoring graphics stats");
107     }
108 
109     /**
110      * Stops monitoring graphics stats by canceling the interval timer
111      */
stopMonitoring()112     public void stopMonitoring () {
113         mIntervalTimer.cancel();
114 
115         mIsRunning = false;
116         Log.d(TAG, "Stopped monitoring graphics stats");
117     }
118 
119     /**
120      * Takes a snapshot of the graphics stats and incorporates them into the process stats history
121      */
grabStatsImage()122     public void grabStatsImage () {
123         Log.v(TAG, "Grabbing image of graphics stats");
124         List<JankStat> allStats = gatherGraphicsStats();
125 
126         for (JankStat procStats : allStats) {
127             List<JankStat> history;
128             if (mGraphicsStatsRecords.containsKey(procStats.packageName)) {
129                 history = mGraphicsStatsRecords.get(procStats.packageName);
130                 // Has the process been killed and restarted?
131                 if (procStats.isContinuedFrom(history.get(history.size() - 1))) {
132                     // Process hasn't been killed and restarted; put the data
133                     history.set(history.size() - 1, procStats);
134                     Log.v(TAG, String.format("Process %s stats have not changed, overwriting data.",
135                             procStats.packageName));
136                 } else {
137                     // Process has been killed and restarted; append the data
138                     history.add(procStats);
139                     Log.v(TAG, String.format("Process %s stats were restarted, appending data.",
140                             procStats.packageName));
141                 }
142             } else {
143                 // Initialize the process stats history list
144                 history = new ArrayList<>();
145                 history.add(procStats);
146                 // Put the history list in the JankStats map
147                 mGraphicsStatsRecords.put(procStats.packageName, history);
148                 Log.v(TAG, String.format("New process, %s. Creating jank history.",
149                         procStats.packageName));
150             }
151         }
152     }
153 
154     /**
155      * Aggregates the graphics stats for each process over its history. Merging specifications can
156      * be found in the static method {@link JankStat#mergeStatHistory}.
157      */
aggregateStatsImages()158     public List<JankStat> aggregateStatsImages () {
159         Log.d(TAG, "Aggregating graphics stats history");
160         List<JankStat> mergedStatsList = new ArrayList<JankStat>();
161 
162         for (Map.Entry<String, List<JankStat>> record : mGraphicsStatsRecords.entrySet()) {
163             String proc = record.getKey();
164             List<JankStat> history = record.getValue();
165 
166             Log.v(TAG, String.format("Aggregating stats for %s (%d set%s)", proc, history.size(),
167                     (history.size() > 1 ? "s" : "")));
168 
169             JankStat mergedStats = JankStat.mergeStatHistory(history);
170             mergedStatsList.add(mergedStats);
171         }
172 
173         return mergedStatsList;
174     }
175 
176     /**
177      * Clears all graphics stats history data for all processes
178      */
clearStatsImages()179     public void clearStatsImages () {
180         mGraphicsStatsRecords.clear();
181     }
182 
183     /**
184      * Resets graphics stats for all currently tracked processes
185      */
clearGraphicsStats()186     public void clearGraphicsStats () {
187         Log.d(TAG, "Reset all graphics stats");
188         List<JankStat> existingStats = gatherGraphicsStats();
189         for (JankStat stat : existingStats) {
190             executeShellCommand(String.format("dumpsys gfxinfo %s reset", stat.packageName));
191             Log.v(TAG, String.format("Cleared graphics stats for %s", stat.packageName));
192         }
193     }
194 
195     /**
196      * Return JankStat objects with metric data for all currently tracked processes
197      */
gatherGraphicsStats()198     public List<JankStat> gatherGraphicsStats () {
199         Log.v(TAG, "Gather all graphics stats");
200         List<JankStat> result = new ArrayList<>();
201 
202         BufferedReader stream = executeShellCommand("dumpsys graphicsstats");
203         try {
204             // Read the stream process by process
205             String line;
206 
207             while ((line = stream.readLine()) != null) {
208                 String proc = JankStat.StatPattern.PACKAGE.parse(line);
209                 if (proc != null) {
210                     // "Package: a.b.c"
211                     Log.v(TAG, String.format("Found process, %s. Gathering jank info.", proc));
212                     // "Stats since: ###ns"
213                     line = stream.readLine();
214                     Long since = Long.parseLong(JankStat.StatPattern.STATS_SINCE.parse(line));
215                     // "Total frames rendered: ###"
216                     line = stream.readLine();
217                     int total = Integer.valueOf(JankStat.StatPattern.TOTAL_FRAMES.parse(line));
218                     // "Janky frames: ## (#.#%)" OR
219                     // "Janky frames: ## (nan%)"
220                     line = stream.readLine();
221                     int janky = Integer.valueOf(JankStat.StatPattern.NUM_JANKY.parse(line));
222                     // (optional, N+) "50th percentile: ##ms"
223                     line = stream.readLine();
224                     int perc50;
225                     String parsed50 = JankStat.StatPattern.FRAME_TIME_50TH.parse(line);
226                     if (parsed50 != null || !parsed50.isEmpty()) {
227                         perc50 = Integer.valueOf(parsed50);
228                         line = stream.readLine();
229                     } else {
230                         perc50 = -1;
231                     }
232                     // "90th percentile: ##ms"
233                     int perc90 = Integer.valueOf(JankStat.StatPattern.FRAME_TIME_90TH.parse(line));
234                     // "95th percentile: ##ms"
235                     line = stream.readLine();
236                     int perc95 = Integer.valueOf(JankStat.StatPattern.FRAME_TIME_95TH.parse(line));
237                     // "99th percentile: ##ms"
238                     line = stream.readLine();
239                     int perc99 = Integer.valueOf(JankStat.StatPattern.FRAME_TIME_99TH.parse(line));
240                     // "Slowest frames last 24h: ##ms ##ms ..."
241                     line = stream.readLine();
242                     String slowest = JankStat.StatPattern.SLOWEST_FRAMES_24H.parse(line);
243                     if (slowest != null && !slowest.isEmpty()) {
244                         line = stream.readLine();
245                     }
246                     // "Number Missed Vsync: #"
247                     int vsync = Integer.valueOf(JankStat.StatPattern.NUM_MISSED_VSYNC.parse(line));
248                     // "Number High input latency: #"
249                     line = stream.readLine();
250                     int latency = Integer.valueOf(
251                             JankStat.StatPattern.NUM_HIGH_INPUT_LATENCY.parse(line));
252                     // "Number slow UI thread: #"
253                     line = stream.readLine();
254                     int ui = Integer.valueOf(JankStat.StatPattern.NUM_SLOW_UI_THREAD.parse(line));
255                     // "Number Slow bitmap uploads: #"
256                     line = stream.readLine();
257                     int bmp = Integer.valueOf(
258                             JankStat.StatPattern.NUM_SLOW_BITMAP_UPLOADS.parse(line));
259                     // "Number slow issue draw commands: #"
260                     line = stream.readLine();
261                     int draw = Integer.valueOf(JankStat.StatPattern.NUM_SLOW_DRAW.parse(line));
262 
263                     JankStat stat = new JankStat(proc, since, total, janky, perc50, perc90, perc95,
264                             perc99, slowest, vsync, latency, ui, bmp, draw, 1);
265                     result.add(stat);
266                 }
267             }
268 
269             return result;
270         } catch (IOException exception) {
271             Log.e(TAG, "Error with buffered reader", exception);
272             return null;
273         } finally {
274             try {
275                 if (stream != null) {
276                     stream.close();
277                 }
278             } catch (IOException exception) {
279                 Log.e(TAG, "Error with closing the stream", exception);
280             }
281         }
282     }
283 
284     /**
285      * UiAutomation is included solely for the purpose of executing shell commands
286      */
287     private UiAutomation mUiAutomation;
288 
289     /**
290      * Executes a shell command through UiAutomation and puts the results in an
291      * InputStreamReader that is returned inside a BufferedReader.
292      * @param command the command to be executed in the adb shell
293      * @result a BufferedReader that reads the command output
294      */
executeShellCommand(String command)295     public BufferedReader executeShellCommand (String command) {
296         ParcelFileDescriptor stdout = getUiAutomation().executeShellCommand(command);
297 
298         BufferedReader stream = new BufferedReader(new InputStreamReader(
299                 new ParcelFileDescriptor.AutoCloseInputStream(stdout)));
300         return stream;
301     }
302 
303     /**
304      * Sets the UiAutomation member for shell execution
305      */
setUiAutomation(UiAutomation uiAutomation)306     public void setUiAutomation (UiAutomation uiAutomation) {
307         mUiAutomation = uiAutomation;
308     }
309 
310     /**
311      * @return UiAutomation instance from Aupt instrumentation
312      */
getUiAutomation()313     public UiAutomation getUiAutomation () {
314         return mUiAutomation;
315     }
316 }
317