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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: perf_insights/perf_insights/timeline_based_measurement/rendering_stats.html
diff --git a/perf_insights/perf_insights/timeline_based_measurement/rendering_stats.html b/perf_insights/perf_insights/timeline_based_measurement/rendering_stats.html
new file mode 100644
index 0000000000000000000000000000000000000000..e35b4a2716acf024006e5f3d98cf0e240ec2ded9
--- /dev/null
+++ b/perf_insights/perf_insights/timeline_based_measurement/rendering_stats.html
@@ -0,0 +1,407 @@
+<!DOCTYPE HTML>
dsinclair 2015/09/22 16:08:31 nit: s/HTML/html
+<!--
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
+Copyright (c) 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import"
+ href="/perf_insights/timeline_based_measurement/rendering_frame.html">
+
+<link rel="import" href="/tracing/base/math.html">
+<link rel="import" href="/tracing/base/range.html">
+<link rel="import" href="/tracing/extras/chrome/cc/constants.html">
+
+<script>
+'use strict';
+
+tr.exportTo('pi.tbm', function() {
+ var constants = tr.e.cc.constants;
+ var round = tr.b.round;
+
+ // These are keys used in the 'errors' field dictionary located in
+ // RenderingStats in this file.
+ var APPROXIMATED_PIXEL_ERROR = 'approximatedPixelPercentages';
+ var CHECKERBOARDED_PIXEL_ERROR = 'checkerboardedPixelPercentages';
+
+ /**
+ * Get LatencyInfo trace events from the process's trace buffer that are
+ * within the timeRange.
+ *
+ * Input events dump their LatencyInfo into trace buffer as async trace
+ * event of name starting with "InputLatency". Non-input events with name
+ * starting with "Latency". The trace event has a member 'data' containing
+ * its latency history.
+ **/
+ function getLatencyEvents(process, timeRange) {
+ var latencyEvents = [];
+ if (!process)
+ return latencyEvents;
+ var i = 0;
+ process.iterateAllEvents(function(event) {
+ i += 1;
+ if ((event.title === 'InputLatency' || event.title === 'Latency') &&
+ event.start >= timeRange.min &&
+ event.end <= timeRange.max) {
+ event.subSlices.forEach(function(e) {
+ if (e.args['data'] !== undefined)
+ latencyEvents.push(e);
+ });
+ }
+ });
+ return latencyEvents;
+ }
+
+ /**
+ * Compute input event latencies.
+ *
+ * Input event latency is the time from when the input event is created to
+ * when its resulted page is swap buffered.
+ * Input event on differnt platforms uses different LatencyInfo component to
+ * record its creation timestamp. We go through the following component list
+ * to find the creation timestamp:
+ * 1. INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT -- when event is created in OS
+ * 2. INPUT_EVENT_LATENCY_UI_COMPONENT -- when event reaches Chrome
+ * 3. INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT -- when event reaches
+ * RenderWidget
+
+ * If the latency starts with a
+ * LATENCY_BEGIN_SCROLL_UPDATE_MAIN_COMPONENT component, then it is
+ * classified as a scroll update instead of a normal input latency measure.
+
+ * Returns:
+ * A list sorted by increasing start time of latencies which are tuples of
+ * (input_eventTitle, latency_in_ms).
+ **/
+ function computeEventLatencies(inputEvents) {
+ var inputEventLatencies = [];
+ for (var i = 0; i < inputEvents.length; i++) {
+ var event = inputEvents[i];
+ var data = event.args['data'];
+ var endTime = undefined;
+ var startTime = undefined;
+ if (data[constants.END_COMP_NAME]) {
+ endTime = data[constants.END_COMP_NAME]['time'];
+ [constants.ORIGINAL_COMP_NAME,
+ constants.UI_COMP_NAME,
+ constants.BEGIN_COMP_NAME,
+ constants.BEGIN_SCROLL_UPDATE_COMP_NAME].forEach(function(name) {
+ if (data[name] && startTime === undefined) {
+ startTime = data[name]['time'];
+ }
+ });
+ if (startTime === undefined)
+ throw Error('LatencyInfo has no begin component');
+ var latency = (endTime - startTime) / 1000.0;
+ inputEventLatencies.push({
+ start: startTime,
+ title: event.title,
+ latency: latency
+ });
+ }
+ }
+ inputEventLatencies.sort(
+ function(a, b) {
+ return a.start - b.start;
+ });
+ return inputEventLatencies;
+ }
+
+ /**
+ * Returns true if the process contains at least one
+ * BenchmarkInstrumentation::*RenderingStats event with a frame.
+ **/
+ function hasRenderingStats(process) {
+ if (!process)
+ return false;
+ var processHasRenderingStats = false;
+ process.iterateAllEvents(
+ function(event) {
+ if ((event.title === constants.BENCHMARK_DISPLAY_RENDERING_STATS ||
+ event.title === constants.BENCHMARK_IMPL_THREAD_RENDERING_STATS) &&
+ event.args['data'] && event.args['data']['frame_count'] === 1) {
+ processHasRenderingStats = true;
+ }
+ });
+ return processHasRenderingStats;
+ }
+
+ /* Returns the name of the events used to count frame timestamps. */
+ function getTimestampEventName(process) {
+ if (process.title === 'SurfaceFlinger')
+ return constants.VSYNC_BEFORE;
+
+ var eventTitle = constants.BENCHMARK_IMPL_THREAD_RENDERING_STATS;
+ process.iterateAllEvents(
+ function(event) {
+ if (event.title === constants.BENCHMARK_DISPLAY_RENDERING_STATS &&
+ event.args['data'] !== undefined &&
+ event.args['data']['frame_count'] === 1) {
+ eventTitle = constants.BENCHMARK_DISPLAY_RENDERING_STATS;
+ }
+ });
+ return eventTitle;
+ }
+
+ /**
+ * Utility class for extracting rendering statistics from the timeline (or
+ * other loggin facilities), and providing them in a common format to
+ * classes that compute benchmark metrics from this data.
+ *
+ * Stats are lists of lists of numbers. The outer list stores one list per
+ * timeline range.
+ *
+ * All *_time values are measured in milliseconds.
+ **/
+ function RenderingStats(rendererProcess, browserProcess,
+ surfaceFlingerProcess, timeRanges) {
+ if (timeRanges.length === 0)
+ throw new Error('timeRanges is empty');
+ timeRanges.forEach(function(range) {
+ if (!(range instanceof tr.b.Range))
+ throw new Error('timeRanges must contain only Range objects');
+ });
+ this.refreshPeriod = undefined;
+
+ var timestampProcess;
+ // Find the top level process with rendering stats (browser or renderer).
+ if (surfaceFlingerProcess) {
+ timestampProcess = surfaceFlingerProcess;
+ this.getRefreshPeriodFromSurfaceFlingerProcess_(
+ surfaceFlingerProcess);
+ } else if (hasRenderingStats(browserProcess)) {
+ timestampProcess = browserProcess;
+ } else {
+ timestampProcess = rendererProcess;
+ }
+
+ var timestampEventTitle = getTimestampEventName(timestampProcess);
+
+ // A lookup from list names below to any errors or exceptions encountered
+ // in attempting to generate that list.
+ this.errors = {};
+
+ this.frameTimestamps_ = [];
+ this.frameTimes_ = [];
+ this.approximatedPixelPercentages_ = [];
+ this.checkerboardedPixelPercentages_ = [];
+
+ // End-to-end latency for input event - from when input event is
+ // generated to when the its resulted page is swap buffered.
+ this.inputEventLatency_ = [];
+ this.frameQueueingDurations_ = [];
+
+ // Latency from when a scroll update is sent to the main thread until the
+ // resulting frame is swapped.
+ this.scrollUpdateLatency_ = [];
+
+ // Latency for a GestureScrollUpdate input event.
+ this.gestureScrollUpdateLatency_ = [];
+
+ for (var i = 0; i < timeRanges.length; i++) {
+ var timeRange = timeRanges[i];
+ this.frameTimestamps_.push([]);
+ this.frameTimes_.push([]);
+ this.approximatedPixelPercentages_.push([]);
+ this.checkerboardedPixelPercentages_.push([]);
+ this.inputEventLatency_.push([]);
+ this.scrollUpdateLatency_.push([]);
+ this.gestureScrollUpdateLatency_.push([]);
+
+ this.initFrameTimestampsFromTimeline_(
+ timestampProcess, timestampEventTitle, timeRange);
+ this.initImplThreadRenderingStatsFromTimeline_(
+ rendererProcess, timeRange);
+ this.initInputLatencyStatsFromTimeline_(
+ browserProcess, rendererProcess, timeRange);
+ this.initFrameQueueingDurationsFromTimeline_(
+ rendererProcess, timeRange);
+ }
+
+ }
+
+ RenderingStats.prototype = {
+
+ get frameTimestamps() {
+ return this.frameTimestamps_;
+ },
+
+ get frameTimes() {
+ return this.frameTimes_;
+ },
+
+ get approximatedPixelPercentages() {
+ return this.approximatedPixelPercentages_;
+ },
+
+ get checkerboardedPixelPercentages() {
+ return this.checkerboardedPixelPercentages_;
+ },
+
+ get inputEventLatency() {
+ return this.inputEventLatency_;
+ },
+
+ get frameQueueingDurations() {
+ return this.frameQueueingDurations_;
+ },
+
+ get scrollUpdateLatency() {
+ return this.scrollUpdateLatency_;
+ },
+
+ get gestureScrollUpdateLatency() {
+ return this.gestureScrollUpdateLatency_;
+ },
+
+ getRefreshPeriodFromSurfaceFlingerProcess_: function(
+ surfaceFlingerProcess) {
+ surfaceFlingerProcess.iterateAllEvents(
+ function(event) {
+ if (event.title === constants.VSYNC_BEFORE) {
+ this.refreshPeriod = event.args['data'].refreshPeriod;
+ }
+ }, this);
+ },
+
+ initInputLatencyStatsFromTimeline_: function(browserProcess,
+ rendererProcess, timeRange) {
+ var latencyEvents = getLatencyEvents(browserProcess, timeRange);
+ // Plugin input event's latency slice is generated in renderer process.
+ latencyEvents.push.apply(latencyEvents,
+ getLatencyEvents(rendererProcess, timeRange));
+ var eventLatencies = computeEventLatencies(latencyEvents);
+ // Don't include scroll updates in the overall input latency
+ // measurement, because scroll updates can take much more time to
+ // process than other input events and would therefore add noise to
+ // overall latency numbers.
+
+ eventLatencies.forEach(function(event) {
+ if (event.title !== constants.SCROLL_UPDATE_EVENT_NAME) {
+ this.inputEventLatency_[this.inputEventLatency_.length - 1].push(
+ event.latency);
+ }
+ if (event.title === constants.SCROLL_UPDATE_EVENT_NAME) {
+ this.scrollUpdateLatency_[this.scrollUpdateLatency_.length - 1].push(
+ event.latency);
+ }
+ if (event.title === constants.GESTURE_SCROLL_UPDATE_EVENT_NAME) {
+ this.gestureScrollUpdateLatency_[
+ this.gestureScrollUpdateLatency_.length - 1].push(event.latency);
+ }
+ }, this);
+ },
+
+ gatherEvents_: function(eventTitle, process, timeRange) {
+ var events = [];
+ process.iterateAllEvents(function(event) {
+ if (event.title === eventTitle &&
+ event.start >= timeRange.min &&
+ event.end <= timeRange.max &&
+ event.args['data'] !== undefined)
+ events.push(event);
+ });
+ events.sort(function(a, b) {
+ return a.start - b.start;
+ });
+ return events;
+ },
+
+ addFrameTimestamp_: function(event) {
+ var frameCount = event.args['data']['frame_count'];
+ if (frameCount > 1)
+ throw Error('trace contains multi-frame render stats');
+ if (frameCount === 0)
+ return;
+ var lastFrameTimestamps =
+ this.frameTimestamps_[this.frameTimestamps_.length - 1];
+ lastFrameTimestamps.push(event.start);
+ if (lastFrameTimestamps.length >= 2) {
+ this.frameTimes_[this.frameTimes_.length - 1].push(
+ lastFrameTimestamps[lastFrameTimestamps.length - 1] -
+ lastFrameTimestamps[lastFrameTimestamps.length - 2]);
+ }
+ },
+
+ initFrameTimestampsFromTimeline_: function(
+ process, timestampEventTitle, timeRange) {
+ this.gatherEvents_(
+ timestampEventTitle, process, timeRange).forEach(
+ function(event) {
+ this.addFrameTimestamp_(event);
+ }, this);
+ },
+
+ initImplThreadRenderingStatsFromTimeline_: function(
+ process, timeRange) {
+ var eventTitle = constants.BENCHMARK_IMPL_THREAD_RENDERING_STATS;
+ this.gatherEvents_(eventTitle, process, timeRange).forEach(
+ function(event) {
+ var data = event.args['data'];
+ if (data[constants.VISIBLE_CONTENT_DATA] === undefined) {
+ this.errors[APPROXIMATED_PIXEL_ERROR] =
+ 'Calculating approximatedPixelPercentages not possible ' +
+ 'because visible_content_area was missing.';
+ this.errors[CHECKERBOARDED_PIXEL_ERROR] =
+ 'Calculating checkerboardedPixelPercentages not possible ' +
+ 'because visible_content_area was missing.';
+ return;
+ }
+ var visible_content_area = data[constants.VISIBLE_CONTENT_DATA];
+ if (visible_content_area === 0) {
+ this.errors[APPROXIMATED_PIXEL_ERROR] =
+ 'Calculating approximatedPixelPercentages would have ' +
+ 'caused a divide-by-zero';
+ this.errors[CHECKERBOARDED_PIXEL_ERROR] =
+ 'Calculating checkerboardedPixelPercentages would have ' +
+ 'caused a divide-by-zero';
+ return;
+ }
+ if (constants.APPROXIMATED_VISIBLE_CONTENT_DATA in data) {
+ var last_index = this.approximatedPixelPercentages_.length - 1;
+ this.approximatedPixelPercentages_[last_index].push(
+ round(data[constants.APPROXIMATED_VISIBLE_CONTENT_DATA] /
+ data[constants.VISIBLE_CONTENT_DATA] * 100.0, 3));
+ } else {
+ this.errors[APPROXIMATED_PIXEL_ERROR] = (
+ 'approximatedPixelPercentages was not recorded');
+ }
+ if (constants.CHECKERBOARDED_VISIBLE_CONTENT_DATA in data) {
+ var last_index = this.checkerboardedPixelPercentages.length - 1;
+ this.checkerboardedPixelPercentages[last_index].push(
+ round(data[constants.CHECKERBOARDED_VISIBLE_CONTENT_DATA] /
+ data[constants.VISIBLE_CONTENT_DATA] * 100.0, 3));
+ } else {
+ this.errors[CHECKERBOARDED_PIXEL_ERROR] =
+ 'checkerboardedPixelPercentages was not recorded';
+ }
+ }, this);
+ },
+
+ initFrameQueueingDurationsFromTimeline_: function(process, timeRange) {
+ try {
+ var events =
+ pi.tbm.RenderingFrame.getFrameEventsInsideRange(
+ process, timeRange);
+ var new_frameQueueingDurations = events.map(function(e) {
+ return e.queueing_duration;
+ });
+ this.frameQueueingDurations_.push(new_frameQueueingDurations);
+ } catch (e) {
+ this.errors['frameQueueingDurations'] =
+ 'Current chrome version does not support the queueing ' +
+ ' delay metric. Error: ' + e.message;
+ }
+ }
+ };
+
+ return {
+ RenderingStats: RenderingStats,
+ RenderingStatsHelpers: {
+ getLatencyEvents: getLatencyEvents,
+ computeEventLatencies: computeEventLatencies,
+ hasRenderingStats: hasRenderingStats
+ }
+ };
+});
+</script>

Powered by Google App Engine
This is Rietveld 408576698