Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 <!DOCTYPE HTML> | |
|
dsinclair
2015/09/22 16:08:31
nit: s/HTML/html
| |
| 2 <!-- | |
|
nduca
2015/09/21 19:55:15
i think we should probably put tbm stuff in metric
nednguyen
2015/09/21 20:09:54
Do you mean mappers/ folder? I don't see any metri
| |
| 3 Copyright (c) 2015 The Chromium Authors. All rights reserved. | |
| 4 Use of this source code is governed by a BSD-style license that can be | |
| 5 found in the LICENSE file. | |
| 6 --> | |
| 7 | |
| 8 <link rel="import" | |
| 9 href="/perf_insights/timeline_based_measurement/rendering_frame.html"> | |
| 10 | |
| 11 <link rel="import" href="/tracing/base/math.html"> | |
| 12 <link rel="import" href="/tracing/base/range.html"> | |
| 13 <link rel="import" href="/tracing/extras/chrome/cc/constants.html"> | |
| 14 | |
| 15 <script> | |
| 16 'use strict'; | |
| 17 | |
| 18 tr.exportTo('pi.tbm', function() { | |
| 19 var constants = tr.e.cc.constants; | |
| 20 var round = tr.b.round; | |
| 21 | |
| 22 // These are keys used in the 'errors' field dictionary located in | |
| 23 // RenderingStats in this file. | |
| 24 var APPROXIMATED_PIXEL_ERROR = 'approximatedPixelPercentages'; | |
| 25 var CHECKERBOARDED_PIXEL_ERROR = 'checkerboardedPixelPercentages'; | |
| 26 | |
| 27 /** | |
| 28 * Get LatencyInfo trace events from the process's trace buffer that are | |
| 29 * within the timeRange. | |
| 30 * | |
| 31 * Input events dump their LatencyInfo into trace buffer as async trace | |
| 32 * event of name starting with "InputLatency". Non-input events with name | |
| 33 * starting with "Latency". The trace event has a member 'data' containing | |
| 34 * its latency history. | |
| 35 **/ | |
| 36 function getLatencyEvents(process, timeRange) { | |
| 37 var latencyEvents = []; | |
| 38 if (!process) | |
| 39 return latencyEvents; | |
| 40 var i = 0; | |
| 41 process.iterateAllEvents(function(event) { | |
| 42 i += 1; | |
| 43 if ((event.title === 'InputLatency' || event.title === 'Latency') && | |
| 44 event.start >= timeRange.min && | |
| 45 event.end <= timeRange.max) { | |
| 46 event.subSlices.forEach(function(e) { | |
| 47 if (e.args['data'] !== undefined) | |
| 48 latencyEvents.push(e); | |
| 49 }); | |
| 50 } | |
| 51 }); | |
| 52 return latencyEvents; | |
| 53 } | |
| 54 | |
| 55 /** | |
| 56 * Compute input event latencies. | |
| 57 * | |
| 58 * Input event latency is the time from when the input event is created to | |
| 59 * when its resulted page is swap buffered. | |
| 60 * Input event on differnt platforms uses different LatencyInfo component to | |
| 61 * record its creation timestamp. We go through the following component list | |
| 62 * to find the creation timestamp: | |
| 63 * 1. INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT -- when event is created in OS | |
| 64 * 2. INPUT_EVENT_LATENCY_UI_COMPONENT -- when event reaches Chrome | |
| 65 * 3. INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT -- when event reaches | |
| 66 * RenderWidget | |
| 67 | |
| 68 * If the latency starts with a | |
| 69 * LATENCY_BEGIN_SCROLL_UPDATE_MAIN_COMPONENT component, then it is | |
| 70 * classified as a scroll update instead of a normal input latency measure. | |
| 71 | |
| 72 * Returns: | |
| 73 * A list sorted by increasing start time of latencies which are tuples of | |
| 74 * (input_eventTitle, latency_in_ms). | |
| 75 **/ | |
| 76 function computeEventLatencies(inputEvents) { | |
| 77 var inputEventLatencies = []; | |
| 78 for (var i = 0; i < inputEvents.length; i++) { | |
| 79 var event = inputEvents[i]; | |
| 80 var data = event.args['data']; | |
| 81 var endTime = undefined; | |
| 82 var startTime = undefined; | |
| 83 if (data[constants.END_COMP_NAME]) { | |
| 84 endTime = data[constants.END_COMP_NAME]['time']; | |
| 85 [constants.ORIGINAL_COMP_NAME, | |
| 86 constants.UI_COMP_NAME, | |
| 87 constants.BEGIN_COMP_NAME, | |
| 88 constants.BEGIN_SCROLL_UPDATE_COMP_NAME].forEach(function(name) { | |
| 89 if (data[name] && startTime === undefined) { | |
| 90 startTime = data[name]['time']; | |
| 91 } | |
| 92 }); | |
| 93 if (startTime === undefined) | |
| 94 throw Error('LatencyInfo has no begin component'); | |
| 95 var latency = (endTime - startTime) / 1000.0; | |
| 96 inputEventLatencies.push({ | |
| 97 start: startTime, | |
| 98 title: event.title, | |
| 99 latency: latency | |
| 100 }); | |
| 101 } | |
| 102 } | |
| 103 inputEventLatencies.sort( | |
| 104 function(a, b) { | |
| 105 return a.start - b.start; | |
| 106 }); | |
| 107 return inputEventLatencies; | |
| 108 } | |
| 109 | |
| 110 /** | |
| 111 * Returns true if the process contains at least one | |
| 112 * BenchmarkInstrumentation::*RenderingStats event with a frame. | |
| 113 **/ | |
| 114 function hasRenderingStats(process) { | |
| 115 if (!process) | |
| 116 return false; | |
| 117 var processHasRenderingStats = false; | |
| 118 process.iterateAllEvents( | |
| 119 function(event) { | |
| 120 if ((event.title === constants.BENCHMARK_DISPLAY_RENDERING_STATS || | |
| 121 event.title === constants.BENCHMARK_IMPL_THREAD_RENDERING_STATS) && | |
| 122 event.args['data'] && event.args['data']['frame_count'] === 1) { | |
| 123 processHasRenderingStats = true; | |
| 124 } | |
| 125 }); | |
| 126 return processHasRenderingStats; | |
| 127 } | |
| 128 | |
| 129 /* Returns the name of the events used to count frame timestamps. */ | |
| 130 function getTimestampEventName(process) { | |
| 131 if (process.title === 'SurfaceFlinger') | |
| 132 return constants.VSYNC_BEFORE; | |
| 133 | |
| 134 var eventTitle = constants.BENCHMARK_IMPL_THREAD_RENDERING_STATS; | |
| 135 process.iterateAllEvents( | |
| 136 function(event) { | |
| 137 if (event.title === constants.BENCHMARK_DISPLAY_RENDERING_STATS && | |
| 138 event.args['data'] !== undefined && | |
| 139 event.args['data']['frame_count'] === 1) { | |
| 140 eventTitle = constants.BENCHMARK_DISPLAY_RENDERING_STATS; | |
| 141 } | |
| 142 }); | |
| 143 return eventTitle; | |
| 144 } | |
| 145 | |
| 146 /** | |
| 147 * Utility class for extracting rendering statistics from the timeline (or | |
| 148 * other loggin facilities), and providing them in a common format to | |
| 149 * classes that compute benchmark metrics from this data. | |
| 150 * | |
| 151 * Stats are lists of lists of numbers. The outer list stores one list per | |
| 152 * timeline range. | |
| 153 * | |
| 154 * All *_time values are measured in milliseconds. | |
| 155 **/ | |
| 156 function RenderingStats(rendererProcess, browserProcess, | |
| 157 surfaceFlingerProcess, timeRanges) { | |
| 158 if (timeRanges.length === 0) | |
| 159 throw new Error('timeRanges is empty'); | |
| 160 timeRanges.forEach(function(range) { | |
| 161 if (!(range instanceof tr.b.Range)) | |
| 162 throw new Error('timeRanges must contain only Range objects'); | |
| 163 }); | |
| 164 this.refreshPeriod = undefined; | |
| 165 | |
| 166 var timestampProcess; | |
| 167 // Find the top level process with rendering stats (browser or renderer). | |
| 168 if (surfaceFlingerProcess) { | |
| 169 timestampProcess = surfaceFlingerProcess; | |
| 170 this.getRefreshPeriodFromSurfaceFlingerProcess_( | |
| 171 surfaceFlingerProcess); | |
| 172 } else if (hasRenderingStats(browserProcess)) { | |
| 173 timestampProcess = browserProcess; | |
| 174 } else { | |
| 175 timestampProcess = rendererProcess; | |
| 176 } | |
| 177 | |
| 178 var timestampEventTitle = getTimestampEventName(timestampProcess); | |
| 179 | |
| 180 // A lookup from list names below to any errors or exceptions encountered | |
| 181 // in attempting to generate that list. | |
| 182 this.errors = {}; | |
| 183 | |
| 184 this.frameTimestamps_ = []; | |
| 185 this.frameTimes_ = []; | |
| 186 this.approximatedPixelPercentages_ = []; | |
| 187 this.checkerboardedPixelPercentages_ = []; | |
| 188 | |
| 189 // End-to-end latency for input event - from when input event is | |
| 190 // generated to when the its resulted page is swap buffered. | |
| 191 this.inputEventLatency_ = []; | |
| 192 this.frameQueueingDurations_ = []; | |
| 193 | |
| 194 // Latency from when a scroll update is sent to the main thread until the | |
| 195 // resulting frame is swapped. | |
| 196 this.scrollUpdateLatency_ = []; | |
| 197 | |
| 198 // Latency for a GestureScrollUpdate input event. | |
| 199 this.gestureScrollUpdateLatency_ = []; | |
| 200 | |
| 201 for (var i = 0; i < timeRanges.length; i++) { | |
| 202 var timeRange = timeRanges[i]; | |
| 203 this.frameTimestamps_.push([]); | |
| 204 this.frameTimes_.push([]); | |
| 205 this.approximatedPixelPercentages_.push([]); | |
| 206 this.checkerboardedPixelPercentages_.push([]); | |
| 207 this.inputEventLatency_.push([]); | |
| 208 this.scrollUpdateLatency_.push([]); | |
| 209 this.gestureScrollUpdateLatency_.push([]); | |
| 210 | |
| 211 this.initFrameTimestampsFromTimeline_( | |
| 212 timestampProcess, timestampEventTitle, timeRange); | |
| 213 this.initImplThreadRenderingStatsFromTimeline_( | |
| 214 rendererProcess, timeRange); | |
| 215 this.initInputLatencyStatsFromTimeline_( | |
| 216 browserProcess, rendererProcess, timeRange); | |
| 217 this.initFrameQueueingDurationsFromTimeline_( | |
| 218 rendererProcess, timeRange); | |
| 219 } | |
| 220 | |
| 221 } | |
| 222 | |
| 223 RenderingStats.prototype = { | |
| 224 | |
| 225 get frameTimestamps() { | |
| 226 return this.frameTimestamps_; | |
| 227 }, | |
| 228 | |
| 229 get frameTimes() { | |
| 230 return this.frameTimes_; | |
| 231 }, | |
| 232 | |
| 233 get approximatedPixelPercentages() { | |
| 234 return this.approximatedPixelPercentages_; | |
| 235 }, | |
| 236 | |
| 237 get checkerboardedPixelPercentages() { | |
| 238 return this.checkerboardedPixelPercentages_; | |
| 239 }, | |
| 240 | |
| 241 get inputEventLatency() { | |
| 242 return this.inputEventLatency_; | |
| 243 }, | |
| 244 | |
| 245 get frameQueueingDurations() { | |
| 246 return this.frameQueueingDurations_; | |
| 247 }, | |
| 248 | |
| 249 get scrollUpdateLatency() { | |
| 250 return this.scrollUpdateLatency_; | |
| 251 }, | |
| 252 | |
| 253 get gestureScrollUpdateLatency() { | |
| 254 return this.gestureScrollUpdateLatency_; | |
| 255 }, | |
| 256 | |
| 257 getRefreshPeriodFromSurfaceFlingerProcess_: function( | |
| 258 surfaceFlingerProcess) { | |
| 259 surfaceFlingerProcess.iterateAllEvents( | |
| 260 function(event) { | |
| 261 if (event.title === constants.VSYNC_BEFORE) { | |
| 262 this.refreshPeriod = event.args['data'].refreshPeriod; | |
| 263 } | |
| 264 }, this); | |
| 265 }, | |
| 266 | |
| 267 initInputLatencyStatsFromTimeline_: function(browserProcess, | |
| 268 rendererProcess, timeRange) { | |
| 269 var latencyEvents = getLatencyEvents(browserProcess, timeRange); | |
| 270 // Plugin input event's latency slice is generated in renderer process. | |
| 271 latencyEvents.push.apply(latencyEvents, | |
| 272 getLatencyEvents(rendererProcess, timeRange)); | |
| 273 var eventLatencies = computeEventLatencies(latencyEvents); | |
| 274 // Don't include scroll updates in the overall input latency | |
| 275 // measurement, because scroll updates can take much more time to | |
| 276 // process than other input events and would therefore add noise to | |
| 277 // overall latency numbers. | |
| 278 | |
| 279 eventLatencies.forEach(function(event) { | |
| 280 if (event.title !== constants.SCROLL_UPDATE_EVENT_NAME) { | |
| 281 this.inputEventLatency_[this.inputEventLatency_.length - 1].push( | |
| 282 event.latency); | |
| 283 } | |
| 284 if (event.title === constants.SCROLL_UPDATE_EVENT_NAME) { | |
| 285 this.scrollUpdateLatency_[this.scrollUpdateLatency_.length - 1].push( | |
| 286 event.latency); | |
| 287 } | |
| 288 if (event.title === constants.GESTURE_SCROLL_UPDATE_EVENT_NAME) { | |
| 289 this.gestureScrollUpdateLatency_[ | |
| 290 this.gestureScrollUpdateLatency_.length - 1].push(event.latency); | |
| 291 } | |
| 292 }, this); | |
| 293 }, | |
| 294 | |
| 295 gatherEvents_: function(eventTitle, process, timeRange) { | |
| 296 var events = []; | |
| 297 process.iterateAllEvents(function(event) { | |
| 298 if (event.title === eventTitle && | |
| 299 event.start >= timeRange.min && | |
| 300 event.end <= timeRange.max && | |
| 301 event.args['data'] !== undefined) | |
| 302 events.push(event); | |
| 303 }); | |
| 304 events.sort(function(a, b) { | |
| 305 return a.start - b.start; | |
| 306 }); | |
| 307 return events; | |
| 308 }, | |
| 309 | |
| 310 addFrameTimestamp_: function(event) { | |
| 311 var frameCount = event.args['data']['frame_count']; | |
| 312 if (frameCount > 1) | |
| 313 throw Error('trace contains multi-frame render stats'); | |
| 314 if (frameCount === 0) | |
| 315 return; | |
| 316 var lastFrameTimestamps = | |
| 317 this.frameTimestamps_[this.frameTimestamps_.length - 1]; | |
| 318 lastFrameTimestamps.push(event.start); | |
| 319 if (lastFrameTimestamps.length >= 2) { | |
| 320 this.frameTimes_[this.frameTimes_.length - 1].push( | |
| 321 lastFrameTimestamps[lastFrameTimestamps.length - 1] - | |
| 322 lastFrameTimestamps[lastFrameTimestamps.length - 2]); | |
| 323 } | |
| 324 }, | |
| 325 | |
| 326 initFrameTimestampsFromTimeline_: function( | |
| 327 process, timestampEventTitle, timeRange) { | |
| 328 this.gatherEvents_( | |
| 329 timestampEventTitle, process, timeRange).forEach( | |
| 330 function(event) { | |
| 331 this.addFrameTimestamp_(event); | |
| 332 }, this); | |
| 333 }, | |
| 334 | |
| 335 initImplThreadRenderingStatsFromTimeline_: function( | |
| 336 process, timeRange) { | |
| 337 var eventTitle = constants.BENCHMARK_IMPL_THREAD_RENDERING_STATS; | |
| 338 this.gatherEvents_(eventTitle, process, timeRange).forEach( | |
| 339 function(event) { | |
| 340 var data = event.args['data']; | |
| 341 if (data[constants.VISIBLE_CONTENT_DATA] === undefined) { | |
| 342 this.errors[APPROXIMATED_PIXEL_ERROR] = | |
| 343 'Calculating approximatedPixelPercentages not possible ' + | |
| 344 'because visible_content_area was missing.'; | |
| 345 this.errors[CHECKERBOARDED_PIXEL_ERROR] = | |
| 346 'Calculating checkerboardedPixelPercentages not possible ' + | |
| 347 'because visible_content_area was missing.'; | |
| 348 return; | |
| 349 } | |
| 350 var visible_content_area = data[constants.VISIBLE_CONTENT_DATA]; | |
| 351 if (visible_content_area === 0) { | |
| 352 this.errors[APPROXIMATED_PIXEL_ERROR] = | |
| 353 'Calculating approximatedPixelPercentages would have ' + | |
| 354 'caused a divide-by-zero'; | |
| 355 this.errors[CHECKERBOARDED_PIXEL_ERROR] = | |
| 356 'Calculating checkerboardedPixelPercentages would have ' + | |
| 357 'caused a divide-by-zero'; | |
| 358 return; | |
| 359 } | |
| 360 if (constants.APPROXIMATED_VISIBLE_CONTENT_DATA in data) { | |
| 361 var last_index = this.approximatedPixelPercentages_.length - 1; | |
| 362 this.approximatedPixelPercentages_[last_index].push( | |
| 363 round(data[constants.APPROXIMATED_VISIBLE_CONTENT_DATA] / | |
| 364 data[constants.VISIBLE_CONTENT_DATA] * 100.0, 3)); | |
| 365 } else { | |
| 366 this.errors[APPROXIMATED_PIXEL_ERROR] = ( | |
| 367 'approximatedPixelPercentages was not recorded'); | |
| 368 } | |
| 369 if (constants.CHECKERBOARDED_VISIBLE_CONTENT_DATA in data) { | |
| 370 var last_index = this.checkerboardedPixelPercentages.length - 1; | |
| 371 this.checkerboardedPixelPercentages[last_index].push( | |
| 372 round(data[constants.CHECKERBOARDED_VISIBLE_CONTENT_DATA] / | |
| 373 data[constants.VISIBLE_CONTENT_DATA] * 100.0, 3)); | |
| 374 } else { | |
| 375 this.errors[CHECKERBOARDED_PIXEL_ERROR] = | |
| 376 'checkerboardedPixelPercentages was not recorded'; | |
| 377 } | |
| 378 }, this); | |
| 379 }, | |
| 380 | |
| 381 initFrameQueueingDurationsFromTimeline_: function(process, timeRange) { | |
| 382 try { | |
| 383 var events = | |
| 384 pi.tbm.RenderingFrame.getFrameEventsInsideRange( | |
| 385 process, timeRange); | |
| 386 var new_frameQueueingDurations = events.map(function(e) { | |
| 387 return e.queueing_duration; | |
| 388 }); | |
| 389 this.frameQueueingDurations_.push(new_frameQueueingDurations); | |
| 390 } catch (e) { | |
| 391 this.errors['frameQueueingDurations'] = | |
| 392 'Current chrome version does not support the queueing ' + | |
| 393 ' delay metric. Error: ' + e.message; | |
| 394 } | |
| 395 } | |
| 396 }; | |
| 397 | |
| 398 return { | |
| 399 RenderingStats: RenderingStats, | |
| 400 RenderingStatsHelpers: { | |
| 401 getLatencyEvents: getLatencyEvents, | |
| 402 computeEventLatencies: computeEventLatencies, | |
| 403 hasRenderingStats: hasRenderingStats | |
| 404 } | |
| 405 }; | |
| 406 }); | |
| 407 </script> | |
| OLD | NEW |