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