1<!DOCTYPE html>
2<!--
3Copyright (c) 2015 The Chromium Authors. All rights reserved.
4Use of this source code is governed by a BSD-style license that can be
5found in the LICENSE file.
6-->
7<link rel="import" href="/tracing/base/statistics.html">
8<link rel="import" href="/tracing/base/sorted_array_utils.html">
9<link rel="import" href="/tracing/model/frame.html">
10<link rel="import" href="/tracing/base/range_utils.html">
11
12<script>
13'use strict';
14
15/**
16 * @fileoverview Class for managing android-specific model meta data,
17 * such as rendering apps, and frames rendered.
18 */
19tr.exportTo('tr.model.helpers', function() {
20  var Frame = tr.model.Frame;
21  var Statistics = tr.b.Statistics;
22
23  var UI_DRAW_TYPE = {
24    NONE: 'none',
25    LEGACY: 'legacy',
26    MARSHMALLOW: 'marshmallow'
27  };
28
29  var UI_THREAD_DRAW_NAMES = {
30    'performTraversals': UI_DRAW_TYPE.LEGACY,
31    'Choreographer#doFrame': UI_DRAW_TYPE.MARSHMALLOW
32  };
33
34  var RENDER_THREAD_DRAW_NAME = 'DrawFrame';
35  var RENDER_THREAD_INDEP_DRAW_NAME = 'doFrame';
36  var THREAD_SYNC_NAME = 'syncFrameState';
37
38  function getSlicesForThreadTimeRanges(threadTimeRanges) {
39    var ret = [];
40    threadTimeRanges.forEach(function(threadTimeRange) {
41      var slices = [];
42
43      threadTimeRange.thread.sliceGroup.iterSlicesInTimeRange(
44        function(slice) { slices.push(slice); },
45        threadTimeRange.start, threadTimeRange.end);
46      ret.push.apply(ret, slices);
47    });
48    return ret;
49  }
50
51  function makeFrame(threadTimeRanges, surfaceFlinger) {
52    var args = {};
53    if (surfaceFlinger && surfaceFlinger.hasVsyncs) {
54      var start = Statistics.min(threadTimeRanges,
55          function(threadTimeRanges) { return threadTimeRanges.start; });
56      args['deadline'] = surfaceFlinger.getFrameDeadline(start);
57      args['frameKickoff'] = surfaceFlinger.getFrameKickoff(start);
58    }
59    var events = getSlicesForThreadTimeRanges(threadTimeRanges);
60    return new Frame(events, threadTimeRanges, args);
61  }
62
63  function findOverlappingDrawFrame(renderThread, time) {
64    if (!renderThread)
65      return undefined;
66
67    var slices = renderThread.sliceGroup.slices;
68    for (var i = 0; i < slices.length; i++) {
69      var slice = slices[i];
70      if (slice.title == RENDER_THREAD_DRAW_NAME &&
71          slice.start <= time &&
72          time <= slice.end) {
73        return slice;
74      }
75    }
76    return undefined;
77  }
78
79  /**
80   * Builds an array of {start, end} ranges grouping common work of a frame
81   * that occurs just before performTraversals().
82   *
83   * Only necessary before Choreographer#doFrame tracing existed.
84   */
85  function getPreTraversalWorkRanges(uiThread) {
86    if (!uiThread)
87      return [];
88
89    // gather all frame work that occurs outside of performTraversals
90    var preFrameEvents = [];
91    uiThread.sliceGroup.slices.forEach(function(slice) {
92      if (slice.title == 'obtainView' ||
93          slice.title == 'setupListItem' ||
94          slice.title == 'deliverInputEvent' ||
95          slice.title == 'RV Scroll')
96        preFrameEvents.push(slice);
97    });
98    uiThread.asyncSliceGroup.slices.forEach(function(slice) {
99      if (slice.title == 'deliverInputEvent')
100        preFrameEvents.push(slice);
101    });
102
103    return tr.b.mergeRanges(
104        tr.b.convertEventsToRanges(preFrameEvents),
105        3,
106        function(events) {
107      return {
108        start: events[0].min,
109        end: events[events.length - 1].max
110      };
111    });
112  }
113
114  function getFrameStartTime(traversalStart, preTraversalWorkRanges) {
115    var preTraversalWorkRange = tr.b.findClosestIntervalInSortedIntervals(
116        preTraversalWorkRanges,
117        function(range) { return range.start },
118        function(range) { return range.end },
119        traversalStart,
120        3);
121
122    if (preTraversalWorkRange)
123      return preTraversalWorkRange.start;
124    return traversalStart;
125  }
126
127  function getUiThreadDrivenFrames(app) {
128    if (!app.uiThread)
129      return [];
130
131    var preTraversalWorkRanges = [];
132    if (app.uiDrawType == UI_DRAW_TYPE.LEGACY)
133      preTraversalWorkRanges = getPreTraversalWorkRanges(app.uiThread);
134
135    var frames = [];
136    app.uiThread.sliceGroup.slices.forEach(function(slice) {
137      if (!(slice.title in UI_THREAD_DRAW_NAMES)) {
138        return;
139      }
140
141      var threadTimeRanges = [];
142      var uiThreadTimeRange = {
143        thread: app.uiThread,
144        start: getFrameStartTime(slice.start, preTraversalWorkRanges),
145        end: slice.end
146      };
147      threadTimeRanges.push(uiThreadTimeRange);
148
149      // on SDK 21+ devices with RenderThread,
150      // account for time taken on RenderThread
151      var rtDrawSlice = findOverlappingDrawFrame(
152          app.renderThread, slice.end);
153      if (rtDrawSlice) {
154        var rtSyncSlice = rtDrawSlice.findDescendentSlice(THREAD_SYNC_NAME);
155        if (rtSyncSlice) {
156          // Generally, the UI thread is only on the critical path
157          // until the start of sync.
158          uiThreadTimeRange.end = Math.min(uiThreadTimeRange.end,
159                                           rtSyncSlice.start);
160        }
161
162        threadTimeRanges.push({
163          thread: app.renderThread,
164          start: rtDrawSlice.start,
165          end: rtDrawSlice.end
166        });
167      }
168      frames.push(makeFrame(threadTimeRanges, app.surfaceFlinger));
169    });
170    return frames;
171  }
172
173  function getRenderThreadDrivenFrames(app) {
174    if (!app.renderThread)
175      return [];
176
177    var frames = [];
178    app.renderThread.sliceGroup.getSlicesOfName(RENDER_THREAD_INDEP_DRAW_NAME)
179        .forEach(function(slice) {
180      var threadTimeRanges = [{
181        thread: app.renderThread,
182        start: slice.start,
183        end: slice.end
184      }];
185      frames.push(makeFrame(threadTimeRanges, app.surfaceFlinger));
186    });
187    return frames;
188  }
189
190  function getUiDrawType(uiThread) {
191    if (!uiThread)
192      return UI_DRAW_TYPE.NONE;
193
194    var slices = uiThread.sliceGroup.slices;
195    for (var i = 0; i < slices.length; i++) {
196      if (slices[i].title in UI_THREAD_DRAW_NAMES) {
197        return UI_THREAD_DRAW_NAMES[slices[i].title];
198      }
199    }
200    return UI_DRAW_TYPE.NONE;
201  }
202
203  function getInputSamples(process) {
204    var samples = undefined;
205    for (var counterName in process.counters) {
206          if (/^android\.aq\:pending/.test(counterName) &&
207        process.counters[counterName].numSeries == 1) {
208        samples = process.counters[counterName].series[0].samples;
209        break;
210      }
211    }
212
213    if (!samples)
214      return [];
215
216    // output rising edges only, since those are user inputs
217    var inputSamples = [];
218    var lastValue = 0;
219    samples.forEach(function(sample) {
220      if (sample.value > lastValue) {
221        inputSamples.push(sample);
222      }
223      lastValue = sample.value;
224    });
225    return inputSamples;
226  }
227
228  function getAnimationAsyncSlices(uiThread) {
229    if (!uiThread)
230      return [];
231
232    var slices = [];
233    uiThread.asyncSliceGroup.iterateAllEvents(function(slice) {
234      if (/^animator\:/.test(slice.title))
235        slices.push(slice);
236    });
237    return slices;
238  }
239
240  /**
241   * Model for Android App specific data.
242   * @constructor
243   */
244  function AndroidApp(process, uiThread, renderThread, surfaceFlinger,
245      uiDrawType) {
246    this.process = process;
247    this.uiThread = uiThread;
248    this.renderThread = renderThread;
249    this.surfaceFlinger = surfaceFlinger;
250    this.uiDrawType = uiDrawType;
251
252    this.frames_ = undefined;
253    this.inputs_ = undefined;
254  };
255
256  AndroidApp.createForProcessIfPossible = function(process, surfaceFlinger) {
257    var uiThread = process.getThread(process.pid);
258    var uiDrawType = getUiDrawType(uiThread);
259    if (uiDrawType == UI_DRAW_TYPE.NONE) {
260      uiThread = undefined;
261    }
262    var renderThreads = process.findAllThreadsNamed('RenderThread');
263    var renderThread = renderThreads.length == 1 ? renderThreads[0] : undefined;
264
265    if (uiThread || renderThread) {
266      return new AndroidApp(process, uiThread, renderThread, surfaceFlinger,
267        uiDrawType);
268    }
269  };
270
271  AndroidApp.prototype = {
272  /**
273   * Returns a list of all frames in the trace for the app,
274   * constructed on first query.
275   */
276    getFrames: function() {
277      if (!this.frames_) {
278        var uiFrames = getUiThreadDrivenFrames(this);
279        var rtFrames = getRenderThreadDrivenFrames(this);
280        this.frames_ = uiFrames.concat(rtFrames);
281
282        // merge frames by sorting by end timestamp
283        this.frames_.sort(function(a, b) { a.end - b.end });
284      }
285      return this.frames_;
286    },
287
288    /**
289     * Returns list of CounterSamples for each input event enqueued to the app.
290     */
291    getInputSamples: function() {
292      if (!this.inputs_) {
293        this.inputs_ = getInputSamples(this.process);
294      }
295      return this.inputs_;
296    },
297
298    getAnimationAsyncSlices: function() {
299      if (!this.animations_) {
300        this.animations_ = getAnimationAsyncSlices(this.uiThread);
301      }
302      return this.animations_;
303    }
304  };
305
306  return {
307    AndroidApp: AndroidApp
308  };
309});
310</script>
311