1/*
2 * Copyright (C) 2017 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
17package android.support.v4.app;
18
19
20import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
21
22import android.app.Activity;
23import android.os.Build;
24import android.os.Handler;
25import android.os.HandlerThread;
26import android.support.annotation.IntDef;
27import android.support.annotation.NonNull;
28import android.support.annotation.Nullable;
29import android.support.annotation.RequiresApi;
30import android.support.annotation.RestrictTo;
31import android.util.SparseIntArray;
32import android.view.Window;
33
34import java.lang.annotation.Retention;
35import java.lang.annotation.RetentionPolicy;
36import java.lang.ref.WeakReference;
37import java.util.ArrayList;
38
39/**
40 * This class can be used to record and return data about per-frame durations. It returns those
41 * results in an array per metric type, with the results indicating how many samples were
42 * recorded for each duration value. The details of the durations data are described in
43 * {@link #getMetrics()}.
44 * <p>
45 * For more information on the various metrics tracked, see the documentation for the
46 * <a href="https://developer.android.com/reference/android/view/FrameMetrics.html">FrameMetrics
47 * </a> API added in API 24 as well as the
48 * <a href="https://developer.android.com/studio/profile/dev-options-rendering.html">GPU Profiling
49 * guide</a>.
50 */
51public class FrameMetricsAggregator {
52
53    private static final String TAG = "FrameMetrics";
54    private static final boolean DBG = false;
55
56    /**
57     * The index in the metrics array where the data for {@link #TOTAL_DURATION}
58     * is stored.
59     * @see #getMetrics()
60     */
61    public static final int TOTAL_INDEX          = 0;
62    /**
63     * The index in the metrics array where the data for {@link #INPUT_DURATION}
64     * is stored.
65     * @see #getMetrics()
66     */
67    public static final int INPUT_INDEX          = 1;
68    /**
69     * The index in the metrics array where the data for {@link #LAYOUT_MEASURE_DURATION}
70     * is stored.
71     * @see #getMetrics()
72     */
73    public static final int LAYOUT_MEASURE_INDEX = 2;
74    /**
75     * The index in the metrics array where the data for {@link #DRAW_DURATION}
76     * is stored.
77     * @see #getMetrics()
78     */
79    public static final int DRAW_INDEX           = 3;
80    /**
81     * The index in the metrics array where the data for {@link #SYNC_DURATION}
82     * is stored.
83     * @see #getMetrics()
84     */
85    public static final int SYNC_INDEX           = 4;
86    /**
87     * The index in the metrics array where the data for {@link #SYNC_DURATION}
88     * is stored.
89     * @see #getMetrics()
90     */
91    public static final int COMMAND_INDEX        = 5;
92    /**
93     * The index in the metrics array where the data for {@link #COMMAND_DURATION}
94     * is stored.
95     * @see #getMetrics()
96     */
97    public static final int SWAP_INDEX           = 6;
98    /**
99     * The index in the metrics array where the data for {@link #DELAY_DURATION}
100     * is stored.
101     * @see #getMetrics()
102     */
103    public static final int DELAY_INDEX          = 7;
104    /**
105     * The index in the metrics array where the data for {@link #ANIMATION_DURATION}
106     * is stored.
107     * @see #getMetrics()
108     */
109    public static final int ANIMATION_INDEX      = 8;
110    private static final int LAST_INDEX          = 8;
111
112    /**
113     * A flag indicating that the metrics should track the total duration. This
114     * flag may be OR'd with the other flags here when calling {@link #FrameMetricsAggregator(int)}
115     * to indicate all of the metrics that should be tracked for that activity.
116     */
117    public static final int TOTAL_DURATION          = 1 << TOTAL_INDEX;
118    /**
119     * A flag indicating that the metrics should track the input duration. This
120     * flag may be OR'd with the other flags here when calling {@link #FrameMetricsAggregator(int)}
121     * to indicate all of the metrics that should be tracked for that activity.
122     */
123    public static final int INPUT_DURATION          = 1 << INPUT_INDEX;
124    /**
125     * A flag indicating that the metrics should track the layout duration. This
126     * flag may be OR'd with the other flags here when calling {@link #FrameMetricsAggregator(int)}
127     * to indicate all of the metrics that should be tracked for that activity.
128     */
129    public static final int LAYOUT_MEASURE_DURATION = 1 << LAYOUT_MEASURE_INDEX;
130    /**
131     * A flag indicating that the metrics should track the draw duration. This
132     * flag may be OR'd with the other flags here when calling {@link #FrameMetricsAggregator(int)}
133     * to indicate all of the metrics that should be tracked for that activity.
134     */
135    public static final int DRAW_DURATION           = 1 << DRAW_INDEX;
136    /**
137     * A flag indicating that the metrics should track the sync duration. This
138     * flag may be OR'd with the other flags here when calling {@link #FrameMetricsAggregator(int)}
139     * to indicate all of the metrics that should be tracked for that activity.
140     */
141    public static final int SYNC_DURATION           = 1 << SYNC_INDEX;
142    /**
143     * A flag indicating that the metrics should track the command duration. This
144     * flag may be OR'd with the other flags here when calling {@link #FrameMetricsAggregator(int)}
145     * to indicate all of the metrics that should be tracked for that activity.
146     */
147    public static final int COMMAND_DURATION        = 1 << COMMAND_INDEX;
148    /**
149     * A flag indicating that the metrics should track the swap duration. This
150     * flag may be OR'd with the other flags here when calling {@link #FrameMetricsAggregator(int)}
151     * to indicate all of the metrics that should be tracked for that activity.
152     */
153    public static final int SWAP_DURATION           = 1 << SWAP_INDEX;
154    /**
155     * A flag indicating that the metrics should track the delay duration. This
156     * flag may be OR'd with the other flags here when calling {@link #FrameMetricsAggregator(int)}
157     * to indicate all of the metrics that should be tracked for that activity.
158     */
159    public static final int DELAY_DURATION          = 1 << DELAY_INDEX;
160    /**
161     * A flag indicating that the metrics should track the animation duration. This
162     * flag may be OR'd with the other flags here when calling {@link #FrameMetricsAggregator(int)}
163     * to indicate all of the metrics that should be tracked for that activity.
164     */
165    public static final int ANIMATION_DURATION      = 1 << ANIMATION_INDEX;
166    /**
167     * A flag indicating that the metrics should track all durations. This is
168     * a shorthand for OR'ing all of the duration flags. This
169     * flag may be OR'd with the other flags here when calling {@link #FrameMetricsAggregator(int)}
170     * to indicate the metrics that should be tracked for that activity.
171     */
172    public static final int EVERY_DURATION          = 0x1ff;
173
174    private FrameMetricsBaseImpl mInstance;
175
176    /** @hide */
177    @RestrictTo(LIBRARY_GROUP)
178    @Retention(RetentionPolicy.SOURCE)
179    @IntDef(
180            flag = true,
181            value = {
182                    TOTAL_DURATION,
183                    INPUT_DURATION,
184                    LAYOUT_MEASURE_DURATION,
185                    DRAW_DURATION,
186                    SYNC_DURATION,
187                    COMMAND_DURATION,
188                    SWAP_DURATION,
189                    DELAY_DURATION,
190                    ANIMATION_DURATION,
191                    EVERY_DURATION
192            })
193    public @interface MetricType {}
194
195    /**
196     * Constructs a FrameMetricsAggregator object that will track {@link #TOTAL_DURATION}
197     * metrics. If more fine-grained metrics are needed, use {@link #FrameMetricsAggregator(int)}
198     * instead.
199     */
200    public FrameMetricsAggregator() {
201        this(TOTAL_DURATION);
202    }
203
204    /**
205     * Constructs a FrameMetricsAggregator object that will track the metrics specified bty
206     * {@code metricTypeFlags}, which is a value derived by OR'ing together metrics constants
207     * such as {@link #TOTAL_DURATION} to specify all metrics that should be tracked. For example,
208     * {@code TOTAL_DURATION | DRAW_DURATION} will track both the total and draw durations
209     * for every frame.
210     *
211     * @param metricTypeFlags A bitwise collection of flags indicating which metrics should
212     * be recorded.
213     */
214    public FrameMetricsAggregator(@MetricType int metricTypeFlags) {
215        if (Build.VERSION.SDK_INT >= 24) {
216            mInstance = new FrameMetricsApi24Impl(metricTypeFlags);
217        } else {
218            mInstance = new FrameMetricsBaseImpl();
219        }
220    }
221
222    /**
223     * Starts recording frame metrics for the given activity.
224     *
225     * @param activity The Activity object which will have its metrics measured.
226     */
227    public void add(@NonNull Activity activity) {
228        mInstance.add(activity);
229    }
230
231    /**
232     * Stops recording metrics for {@code activity} and returns the collected metrics so far.
233     * Recording will continue if there are still other activities being tracked. Calling
234     * remove() does not reset the metrics array; you must call {@link #reset()} to clear the
235     * data.
236     *
237     * @param activity The Activity to stop tracking metrics for.
238     * @return An array whose index refers to the type of metric stored in that item's
239     * SparseIntArray object, e.g., data for {@code TOTAL_DURATION} is stored in
240     * the {@code [TOTAL_INDEX]} item.
241     * @see #getMetrics()
242     */
243    @Nullable
244    public SparseIntArray[] remove(@NonNull Activity activity) {
245        return mInstance.remove(activity);
246    }
247
248    /**
249     * Stops recording metrics for all Activities currently being tracked. Like {@link
250     * #remove(Activity)}, this method returns the currently-collected metrics. Calling
251     * stop() does not reset the metrics array; you must call {@link #reset()} to clear the
252     * data.
253     *
254     * @return An array whose index refers to the type of metric stored in that item's
255     * SparseIntArray object, e.g., data for {@code TOTAL_DURATION} is stored in
256     * the {@code [TOTAL_INDEX]} item.
257     * @see #remove(Activity)
258     * @see #getMetrics()
259     */
260    @Nullable
261    public SparseIntArray[] stop() {
262        return mInstance.stop();
263    }
264
265    /**
266     * Resets the metrics data and returns the currently-collected metrics.
267     *
268     * @return An array whose index refers to the type of metric stored in that item's
269     * SparseIntArray object, e.g., data for {@code TOTAL_DURATION} is stored in
270     * the {@code [TOTAL_INDEX]} item.
271     * @see #getMetrics()
272     */
273    @Nullable
274    public SparseIntArray[] reset() {
275        return mInstance.reset();
276    }
277
278    /**
279     * Returns the currently-collected metrics in an array of SparseIntArray objects.
280     * The index of the array indicates which metric's data is stored in that
281     * SparseIntArray object. For example, results for total duration will be in
282     * the {@code [TOTAL_INDEX]} item.
283     * <p>
284     * The return value may be null if no metrics were tracked. This is especially true on releases
285     * earlier than API 24, as the FrameMetrics system does not exist on these earlier release.
286     * If the return value is not null, any of the objects at a given index in the array
287     * may still be null, which indicates that data was not being tracked for that type of metric.
288     * For example, if the FrameMetricsAggregator was created with a call to
289     * {@code new FrameMetricsAggregator(TOTAL_DURATION | DRAW_DURATION)}, then the SparseIntArray
290     * at index {@code INPUT_INDEX} will be null.
291     * <p>
292     * For a given non-null SparseIntArray, the results stored are the number of samples at
293     * each millisecond value (rounded). For example, if a data sample consisted of total
294     * durations of 5.1ms, 5.8ms, 6.1ms, and 8.2ms, the SparseIntArray at {@code [TOTAL_DURATION]}
295     * would have key-value pairs (5, 1), (6, 2), (8, 1).
296     *
297     * @return An array whose index refers to the type of metric stored in that item's
298     * SparseIntArray object, e.g., data for {@code TOTAL_DURATION} is stored in
299     * the {@code [TOTAL_INDEX]} item.
300     */
301    @Nullable
302    public SparseIntArray[] getMetrics() {
303        return mInstance.getMetrics();
304    }
305
306    /**
307     * Base implementation noops everything - there's no data to return on pre-API24 releases.
308     */
309    private static class FrameMetricsBaseImpl {
310
311        public void add(Activity activity) {
312        }
313
314        public SparseIntArray[] remove(Activity activity) {
315            return null;
316        }
317
318        public SparseIntArray[] stop() {
319            return null;
320        }
321
322        public SparseIntArray[] getMetrics() {
323            return null;
324        }
325
326        public SparseIntArray[] reset() {
327            return null;
328        }
329    }
330
331    @RequiresApi(24)
332    private static class FrameMetricsApi24Impl extends FrameMetricsBaseImpl {
333
334        private static final int NANOS_PER_MS = 1000000;
335        // rounding value adds half a millisecond, for rounding to nearest ms
336        private static final int NANOS_ROUNDING_VALUE = NANOS_PER_MS / 2;
337        private int mTrackingFlags;
338        private SparseIntArray[] mMetrics = new SparseIntArray[LAST_INDEX + 1];
339        private ArrayList<WeakReference<Activity>> mActivities = new ArrayList<>();
340        private static HandlerThread sHandlerThread = null;
341        private static Handler sHandler = null;
342
343        FrameMetricsApi24Impl(int trackingFlags) {
344            mTrackingFlags = trackingFlags;
345        }
346
347        Window.OnFrameMetricsAvailableListener mListener =
348                new Window.OnFrameMetricsAvailableListener() {
349            @Override
350            public void onFrameMetricsAvailable(Window window,
351                    android.view.FrameMetrics frameMetrics, int dropCountSinceLastInvocation) {
352                if ((mTrackingFlags & TOTAL_DURATION) != 0) {
353                    addDurationItem(mMetrics[TOTAL_INDEX],
354                            frameMetrics.getMetric(android.view.FrameMetrics.TOTAL_DURATION));
355                }
356                if ((mTrackingFlags & INPUT_DURATION) != 0) {
357                    addDurationItem(mMetrics[INPUT_INDEX],
358                            frameMetrics.getMetric(
359                                    android.view.FrameMetrics.INPUT_HANDLING_DURATION));
360                }
361                if ((mTrackingFlags & LAYOUT_MEASURE_DURATION) != 0) {
362                    addDurationItem(mMetrics[LAYOUT_MEASURE_INDEX],
363                            frameMetrics.getMetric(
364                                    android.view.FrameMetrics.LAYOUT_MEASURE_DURATION));
365                }
366                if ((mTrackingFlags & DRAW_DURATION) != 0) {
367                    addDurationItem(mMetrics[DRAW_INDEX],
368                            frameMetrics.getMetric(android.view.FrameMetrics.DRAW_DURATION));
369                }
370                if ((mTrackingFlags & SYNC_DURATION) != 0) {
371                    addDurationItem(mMetrics[SYNC_INDEX],
372                            frameMetrics.getMetric(android.view.FrameMetrics.SYNC_DURATION));
373                }
374                if ((mTrackingFlags & SWAP_DURATION) != 0) {
375                    addDurationItem(mMetrics[SWAP_INDEX],
376                            frameMetrics.getMetric(
377                                    android.view.FrameMetrics.SWAP_BUFFERS_DURATION));
378                }
379                if ((mTrackingFlags & COMMAND_DURATION) != 0) {
380                    addDurationItem(mMetrics[COMMAND_INDEX],
381                            frameMetrics.getMetric(
382                                    android.view.FrameMetrics.COMMAND_ISSUE_DURATION));
383                }
384                if ((mTrackingFlags & DELAY_DURATION) != 0) {
385                    addDurationItem(mMetrics[DELAY_INDEX],
386                            frameMetrics.getMetric(
387                                    android.view.FrameMetrics.UNKNOWN_DELAY_DURATION));
388                }
389                if ((mTrackingFlags & ANIMATION_DURATION) != 0) {
390                    addDurationItem(mMetrics[ANIMATION_INDEX],
391                            frameMetrics.getMetric(
392                                    android.view.FrameMetrics.ANIMATION_DURATION));
393                }
394            }
395        };
396
397        void addDurationItem(SparseIntArray buckets, long duration) {
398            if (buckets != null) {
399                int durationMs = (int) ((duration + NANOS_ROUNDING_VALUE) / NANOS_PER_MS);
400                if (duration >= 0) {
401                    // ignore values < 0; something must have gone wrong
402                    int oldValue = buckets.get(durationMs);
403                    buckets.put(durationMs, (oldValue + 1));
404                }
405            }
406        }
407
408        @Override
409        public void add(Activity activity) {
410            if (sHandlerThread == null) {
411                sHandlerThread = new HandlerThread("FrameMetricsAggregator");
412                sHandlerThread.start();
413                sHandler = new Handler(sHandlerThread.getLooper());
414            }
415            for (int i = 0; i <= LAST_INDEX; ++i) {
416                if (mMetrics[i] == null && (mTrackingFlags & (1 << i)) != 0) {
417                    mMetrics[i] = new SparseIntArray();
418                }
419            }
420            activity.getWindow().addOnFrameMetricsAvailableListener(mListener, sHandler);
421            mActivities.add(new WeakReference<>(activity));
422        }
423
424        @Override
425        public SparseIntArray[] remove(Activity activity) {
426            for (WeakReference<Activity> activityRef : mActivities) {
427                if (activityRef.get() == activity) {
428                    mActivities.remove(activityRef);
429                    break;
430                }
431            }
432            activity.getWindow().removeOnFrameMetricsAvailableListener(mListener);
433            return mMetrics;
434        }
435
436        @Override
437        public SparseIntArray[] stop() {
438            int size = mActivities.size();
439            for (int i = size - 1; i >= 0; i--) {
440                WeakReference<Activity> ref = mActivities.get(i);
441                Activity activity = ref.get();
442                if (ref.get() != null) {
443                    activity.getWindow().removeOnFrameMetricsAvailableListener(mListener);
444                    mActivities.remove(i);
445                }
446            }
447            return mMetrics;
448        }
449
450        @Override
451        public SparseIntArray[] getMetrics() {
452            return mMetrics;
453        }
454
455        @Override
456        public SparseIntArray[] reset() {
457            SparseIntArray[] returnVal = mMetrics;
458            mMetrics = new SparseIntArray[LAST_INDEX + 1];
459            return returnVal;
460        }
461
462    }
463
464}
465