Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(86)

Side by Side Diff: perf_insights/perf_insights/timeline_based_measurement/rendering_stats.html

Issue 1336373002: Port rendering_stats' implementation to javascript (Closed) Base URL: https://github.com/catapult-project/catapult@master
Patch Set: Address Dan's reviews Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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>
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698