Chromium Code Reviews| Index: perf_insights/perf_insights/timeline_based_measurement/rendering_stats_test.html |
| diff --git a/perf_insights/perf_insights/timeline_based_measurement/rendering_stats_test.html b/perf_insights/perf_insights/timeline_based_measurement/rendering_stats_test.html |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..e1a6a39651aaa96222b64a0fc83059dfc7fb96c1 |
| --- /dev/null |
| +++ b/perf_insights/perf_insights/timeline_based_measurement/rendering_stats_test.html |
| @@ -0,0 +1,734 @@ |
| +<!DOCTYPE HTML> |
| +<!-- |
| +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="/tracing/base/math.html"> |
| +<link rel="import" href="/tracing/base/range.html"> |
| +<link rel="import" href="/tracing/base/statistics.html"> |
| +<link rel="import" href="/tracing/base/utils.html"> |
| +<link rel="import" href="/tracing/core/test_utils.html"> |
| +<link rel="import" href="/tracing/extras/chrome/cc/constants.html"> |
| +<link rel="import" href="/tracing/extras/chrome/chrome_test_utils.html"> |
| +<link rel="import" href="/tracing/model/model.html"> |
| + |
| +<link rel="import" |
| + href="/perf_insights/timeline_based_measurement/rendering_stats.html"> |
| + |
| +<script> |
| +'use strict'; |
| + |
| +tr.b.unittest.testSuite(function() { |
| + |
| + var RenderingStatsHelpers = pi.tbm.RenderingStatsHelpers; |
| + var RenderingStats = pi.tbm.RenderingStats; |
| + var Range = tr.b.Range; |
| + var constants = tr.e.cc.constants; |
| + var round = tr.b.round; |
| + var flattenArray = tr.b.flattenArray; |
| + |
| + var test_utils = tr.c.TestUtils; |
| + var ThreadSlice = tr.model.ThreadSlice; |
| + |
| + |
|
dsinclair
2015/09/22 16:08:32
nit: remove blank line
|
| + /** |
| + * A mock timer class. |
| + * |
| + * An instance of this class is used as a global timer for stats and |
| + * consistent timestamps for all mock trace events. |
| + * The unit of time is milliseconds. |
| + **/ |
| + function MockTimer() { |
| + this.milliseconds = 0; |
| + } |
| + |
| + MockTimer.prototype = { |
| + advance: function(low, high, delta) { |
| + this.milliseconds += delta; |
| + return delta; |
| + }, |
| + |
| + advanceAndGet(opt_low, opt_high, opt_delta) { |
| + if (opt_low === undefined) |
| + opt_low = 0.1; |
|
dsinclair
2015/09/22 16:08:32
opt_low = opt_low || 0.1; (although this doesn't
|
| + if (opt_high === undefined) |
| + opt_high = 1.0; |
| + if (opt_delta === undefined) |
| + opt_delta = 0.4; |
| + this.advance(opt_low, opt_high, opt_delta); |
| + return this.milliseconds; |
| + } |
| + }; |
| + |
| + /* Stores expected data for comparison with actual RenderingStats */ |
| + function ReferenceRenderingStats() { |
| + this.frameTimestamps = []; |
| + this.frameTimes = []; |
| + this.approximatedPixelPercentages = []; |
| + this.checkerboardedPixelPercentages = []; |
| + } |
| + |
| + ReferenceRenderingStats.prototype = { |
| + pushNewRange: function() { |
| + this.frameTimestamps.push([]); |
| + this.frameTimes.push([]); |
| + this.approximatedPixelPercentages.push([]); |
| + this.checkerboardedPixelPercentages.push([]); |
| + } |
| + }; |
| + |
| + /* Stores expected data for comparison with actual input latency stats */ |
| + function ReferenceInputLatencyStats() { |
| + this.inputEventLatency = []; |
| + this.input_event = []; |
| + } |
| + |
| + /** |
| + * Adds a random surface flinger stats event. |
| + * |
| + * thread: The timeline model thread to which the event will be added. |
| + * first_frame: Is this the first frame within the bounds of an action? |
| + * opt_ref_stats: A ReferenceRenderingStats object to record expected |
| + * values. |
| + **/ |
| + function addSurfaceFlingerStats( |
| + mock_timer, thread, first_frame, opt_ref_stats) { |
| + |
| + // Create randonm data and timestap for impl thread rendering stats. |
|
dsinclair
2015/09/22 16:08:32
nit: s/randonm/random
nit: s/timestap/timestamp
|
| + var data = { |
| + 'frame_count': 1, |
| + 'refresh_period': 16.6666 |
| + }; |
| + var timestamp = mock_timer.advanceAndGet(); |
| + |
|
dsinclair
2015/09/22 16:08:32
nit: remove blank line
|
| + |
| + // Add a slice with the event data to the given thread. |
| + thread.sliceGroup.pushSlice(test_utils.newSliceEx({ |
| + type: ThreadSlice, |
| + cat: 'SurfaceFlinger', |
| + title: 'vsync_before', |
| + start: timestamp, |
| + duration: 0.0, |
| + args: { |
| + 'data': data |
| + } |
| + })); |
| + |
| + |
| + if (opt_ref_stats === undefined) |
| + return; |
| + |
| + // Add timestamp only if a frame was output |
| + if (data['frame_count'] === 1) { |
|
dsinclair
2015/09/22 16:08:32
if (data.frame_count !== 1)
return;
|
| + var frameTimes = opt_ref_stats.frameTimes; |
| + var frameTimestamps = opt_ref_stats.frameTimestamps; |
| + if (!first_frame) { |
| + // Add frame_time if this is not the first frame in within the bounds |
| + // of an action. |
| + var prev_timestamps_group = |
| + frameTimestamps[frameTimestamps.length - 1]; |
| + var prev_timestamp = |
| + prev_timestamps_group[prev_timestamps_group.length - 1]; |
| + frameTimes[frameTimes.length - 1].push(timestamp - prev_timestamp); |
| + } |
| + frameTimestamps[frameTimestamps.length - 1].push(timestamp); |
| + |
| + } |
| + } |
| + |
| + /** |
| + * Adds a random display rendering stats event. |
| + * |
| + * thread: The timeline model thread to which the event will be added. |
| + * first_frame: Is this the first frame within the bounds of an action? |
|
dsinclair
2015/09/22 16:08:32
Do we know the answer to this?
|
| + * ref_stats: A ReferenceRenderingStats object to record expected values. |
| + **/ |
| + function addDisplayRenderingStats( |
| + mock_timer, thread, first_frame, opt_ref_stats) { |
| + |
| + // Create randonm data and timestap for main thread rendering stats. |
| + var data = { |
| + 'frame_count': 1 |
| + }; |
| + var timestamp = mock_timer.advanceAndGet(); |
| + |
| + // Add a slice with the event data to the given thread. |
| + thread.sliceGroup.pushSlice(test_utils.newSliceEx({ |
| + type: ThreadSlice, |
| + cat: 'benchmark', |
| + title: 'BenchmarkInstrumentation::DisplayRenderingStats', |
| + start: timestamp, |
| + duration: 0.0, |
| + args: { |
| + 'data': data |
| + } |
| + })); |
| + |
| + if (opt_ref_stats === undefined) |
| + return; |
| + var frameTimes = opt_ref_stats.frameTimes; |
| + var frameTimestamps = opt_ref_stats.frameTimestamps; |
| + // Add timestamp only if a frame was output |
| + if (!first_frame) { |
| + // Add frame_time if this is not the first frame in within the bounds of |
| + // an action. |
| + var prev_timestamps_group = |
| + frameTimestamps[frameTimestamps.length - 1]; |
| + var prev_timestamp = |
| + prev_timestamps_group[prev_timestamps_group.length - 1]; |
| + frameTimes[frameTimes.length - 1].push(timestamp - prev_timestamp); |
| + } |
| + frameTimestamps[frameTimestamps.length - 1].push(timestamp); |
| + } |
| + |
| + /** |
| + * Adds a random impl thread rendering stats event. |
| + * |
| + * thread: The model thread to which the event will be added. |
| + * first_frame: Is this the first frame within the bounds of an action? |
| + * opt_ref_stats: A ReferenceRenderingStats object to record expected |
| + * values. |
| + **/ |
| + function addImplThreadRenderingStats( |
| + mock_timer, thread, first_frame, opt_ref_stats) { |
| + |
| + // Create randonm data and timestap for impl thread rendering stats. |
| + var data = { |
| + 'frame_count': 1, |
| + 'visible_content_area': 95, |
| + 'approximated_visible_content_area': 3, |
| + 'checkerboarded_visible_content_area': 4 |
| + }; |
| + var timestamp = mock_timer.advanceAndGet(); |
| + |
| + // Add a slice with the event data to the given thread. |
| + thread.sliceGroup.pushSlice(test_utils.newSliceEx({ |
| + type: ThreadSlice, |
| + cat: 'benchmark', |
| + title: 'BenchmarkInstrumentation::ImplThreadRenderingStats', |
| + start: timestamp, |
| + duration: 0.0, |
| + args: { |
| + 'data': data |
| + } |
| + })); |
| + |
| + if (!opt_ref_stats) |
| + return; |
| + |
| + // Add timestamp only if a frame was output |
| + if (data['frame_count'] === 1) { |
| + if (!first_frame) { |
| + // Add frame_time if this is not the first frame in within the bounds |
| + // of an action. |
| + var lastFrameTimestamps = opt_ref_stats.frameTimestamps[ |
| + opt_ref_stats.frameTimestamps.length - 1]; |
| + var prev_timestamp = lastFrameTimestamps[ |
| + lastFrameTimestamps.length - 1]; |
| + opt_ref_stats.frameTimes[opt_ref_stats.frameTimes.length - 1].push( |
| + timestamp - prev_timestamp); |
| + } |
| + opt_ref_stats.frameTimestamps[ |
| + opt_ref_stats.frameTimestamps.length - 1].push(timestamp); |
| + |
| + opt_ref_stats.approximatedPixelPercentages[ |
| + opt_ref_stats.approximatedPixelPercentages.length - 1].push( |
| + round(tr.b.Statistics.divideIfPossibleOrZero( |
| + data['approximated_visible_content_area'], |
| + data['visible_content_area']) * 100.0, 3)); |
| + |
| + opt_ref_stats.checkerboardedPixelPercentages[ |
| + opt_ref_stats.checkerboardedPixelPercentages.length - 1].push( |
| + round(tr.b.Statistics.divideIfPossibleOrZero( |
| + data['checkerboarded_visible_content_area'], |
| + data['visible_content_area']) * 100.0, 3)); |
| + } |
| + } |
| + |
| + /** |
| + * Adds a random input latency stats event. |
| + * |
| + * start_thread: The start thread on which the async slice is added. |
| + * end_thread: The end thread on which the async slice is ended. |
| + * opt_ref_latency_stats: A ReferenceInputLatencyStats object for expected |
| + * values. |
| + **/ |
| + function addInputLatencyStats( |
| + mock_timer, start_thread, end_thread, opt_ref_latency_stats) { |
| + |
| + var original_comp_time = mock_timer.advanceAndGet(2, 4) * 1000.0; |
| + var ui_comp_time = mock_timer.advanceAndGet(2, 4) * 1000.0; |
| + var begin_comp_time = mock_timer.advanceAndGet(2, 4) * 1000.0; |
| + var forward_comp_time = mock_timer.advanceAndGet(2, 4) * 1000.0; |
| + var end_comp_time = mock_timer.advanceAndGet(10, 20) * 1000.0; |
| + |
| + var data = {}; |
| + data[constants.ORIGINAL_COMP_NAME] = { |
| + 'time': original_comp_time |
| + }; |
| + data[constants.UI_COMP_NAME] = { |
| + 'time': ui_comp_time |
| + }; |
| + data[constants.BEGIN_COMP_NAME] = { |
| + 'time': begin_comp_time |
| + }; |
| + data[constants.END_COMP_NAME] = { |
| + 'time': end_comp_time |
| + }; |
| + |
| + var timestamp = mock_timer.advanceAndGet(2, 4); |
| + |
| + var tracing_async_slice = test_utils.newAsyncSliceEx({ |
| + cat: 'benchmark', |
| + title: 'InputLatency', |
| + start: timestamp, |
| + duration: 0 |
| + }); |
| + |
| + var async_sub_slice = test_utils.newAsyncSliceEx({ |
| + cat: 'benchmark', |
| + title: constants.GESTURE_SCROLL_UPDATE_EVENT_NAME, |
| + start: timestamp, |
| + args: { |
| + 'data': data |
| + }, |
| + duration: 0 |
| + }); |
| + async_sub_slice.parentContainer = tracing_async_slice; |
| + async_sub_slice.startThread = start_thread; |
| + async_sub_slice.endThread = end_thread; |
| + |
| + tracing_async_slice.subSlices.push(async_sub_slice); |
| + tracing_async_slice.startThread = start_thread; |
| + tracing_async_slice.endThread = end_thread; |
| + start_thread.sliceGroup.pushSlice(tracing_async_slice); |
| + |
| + // Add scroll update latency info. |
| + var scroll_update_data = {}; |
| + scroll_update_data[ |
| + constants.BEGIN_SCROLL_UPDATE_COMP_NAME] = { |
| + 'time': begin_comp_time |
| + }; |
| + scroll_update_data[ |
| + constants.FORWARD_SCROLL_UPDATE_COMP_NAME] = { |
| + 'time': forward_comp_time |
| + }; |
| + scroll_update_data[constants.END_COMP_NAME] = { |
| + 'time': end_comp_time |
| + }; |
| + |
| + var scroll_async_slice = test_utils.newAsyncSliceEx({ |
| + cat: 'benchmark', |
| + title: 'InputLatency', |
| + start: timestamp, |
| + duration: 0 |
| + }); |
| + |
| + var scroll_async_sub_slice = test_utils.newAsyncSliceEx({ |
| + cat: 'benchmark', |
| + title: constants.SCROLL_UPDATE_EVENT_NAME, |
| + start: timestamp, |
| + args: { |
| + 'data': scroll_update_data |
| + }, |
| + duration: 0 |
| + }); |
| + scroll_async_sub_slice.parentContainer = scroll_async_slice; |
| + scroll_async_sub_slice.startThread = start_thread; |
| + scroll_async_sub_slice.endThread = end_thread; |
| + |
| + scroll_async_slice.subSlices.push(scroll_async_sub_slice); |
| + scroll_async_slice.startThread = start_thread; |
| + scroll_async_slice.endThread = end_thread; |
| + start_thread.sliceGroup.pushSlice(scroll_async_slice); |
| + |
| + // Also add some dummy frame statistics so we can feed the resulting |
| + // timeline to RenderingStats. |
| + addImplThreadRenderingStats(mock_timer, end_thread, false); |
| + |
| + if (opt_ref_latency_stats === undefined) |
| + return; |
| + |
| + opt_ref_latency_stats.input_event.push(async_sub_slice); |
| + opt_ref_latency_stats.input_event.push( |
| + scroll_async_sub_slice); |
| + opt_ref_latency_stats.inputEventLatency.push({ |
| + eventTitle: constants.GESTURE_SCROLL_UPDATE_EVENT_NAME, |
| + latency: (data[constants.END_COMP_NAME]['time'] - |
| + data[constants.ORIGINAL_COMP_NAME]['time']) / 1000.0 |
| + }); |
| + var scroll_update_time = ( |
| + scroll_update_data[constants.END_COMP_NAME].time - |
| + scroll_update_data[ |
| + constants.BEGIN_SCROLL_UPDATE_COMP_NAME].time); |
| + opt_ref_latency_stats.inputEventLatency.push({ |
| + eventTitle: constants.SCROLL_UPDATE_EVENT_NAME, |
| + latency: scroll_update_time / 1000.0 |
| + }); |
| + }; |
| + |
| + test('hasRenderingStats', function() { |
| + var model = new tr.Model(); |
| + var timer = new MockTimer(); |
| + |
| + // A process without rendering stats |
| + var process_without_stats = model.getOrCreateProcess(1); |
| + var thread_without_stats = process_without_stats.getOrCreateThread( |
| + 11); |
| + model.updateBounds(); |
| + assert.equal( |
| + RenderingStatsHelpers.hasRenderingStats(thread_without_stats), |
| + false); |
| + |
| + // A process with rendering stats, but no frames in them |
| + var process_without_frames = model.getOrCreateProcess(2); |
| + var thread_without_frames = process_without_frames.getOrCreateThread( |
| + 21); |
| + process_without_frames.updateBounds(); |
| + assert.equal( |
| + RenderingStatsHelpers.hasRenderingStats(thread_without_frames), |
| + false); |
| + |
| + // A process with rendering stats and frames in them |
| + var process_with_frames = model.getOrCreateProcess(3); |
| + var thread_with_frames = process_with_frames.getOrCreateThread(31); |
| + addImplThreadRenderingStats(timer, thread_with_frames, true); |
| + process_with_frames.updateBounds(); |
| + assert.equal( |
| + RenderingStatsHelpers.hasRenderingStats(thread_with_frames), |
| + true); |
| + |
| + }); |
| + |
| + test('bothSurfaceFlingerAndDisplayStats', function() { |
| + var model = new tr.Model(); |
| + var timer = new MockTimer(); |
| + |
| + var ref_stats = new ReferenceRenderingStats(); |
| + ref_stats.pushNewRange(); |
| + var surface_flinger = model.getOrCreateProcess(4); |
| + surface_flinger.title = 'SurfaceFlinger'; |
| + var surface_flinger_thread = surface_flinger.getOrCreateThread(41); |
| + var renderer = model.getOrCreateProcess(2); |
| + var browser = model.getOrCreateProcess(3); |
| + var browser_main = browser.getOrCreateThread(31); |
| + browser_main.sliceGroup.beginSlice('webkit.console', 'ActionA', |
| + timer.advanceAndGet(2, 4), ''); |
| + |
| + // Create SurfaceFlinger stats and display rendering stats. |
| + for (var i = 0; i < 10; i++) { |
| + var first = i === 0; |
| + addSurfaceFlingerStats(timer, surface_flinger_thread, first, |
| + ref_stats); |
| + timer.advance(2, 4, 0.5); |
| + } |
| + |
| + for (var i = 0; i < 10; i++) { |
| + var first = i === 0; |
| + addDisplayRenderingStats(timer, browser_main, first); |
| + timer.advance(5, 10, 0.1); |
| + } |
| + |
| + browser_main.sliceGroup.endSlice(timer.advanceAndGet()); |
| + timer.advance(2, 4, 0.2); |
| + |
| + browser.updateBounds(); |
| + renderer.updateBounds(); |
| + var timelineRanges = []; |
| + model.iterateAllEvents(function(event) { |
| + if (event.title === 'ActionA') { |
| + timelineRanges.push(Range.fromExplicitRange(event.start, event.end)); |
| + } |
| + }); |
| + var stats = new RenderingStats( |
| + renderer, browser, surface_flinger, timelineRanges); |
| + |
| + // Compare rendering stats to reference - Only SurfaceFlinger stats should |
| + // count |
| + assert.deepEqual(stats.frameTimestamps, ref_stats.frameTimestamps); |
| + assert.deepEqual(stats.frameTimes, ref_stats.frameTimes); |
| + }); |
| + |
| + test('bothDisplayAndImplStats', function() { |
| + var model = new tr.Model(); |
| + var timer = new MockTimer(); |
| + |
| + var ref_stats = new ReferenceRenderingStats(); |
| + ref_stats.pushNewRange(); |
| + var renderer = model.getOrCreateProcess(2); |
| + var browser = model.getOrCreateProcess(3); |
| + var browser_main = browser.getOrCreateThread(31); |
| + browser_main.sliceGroup.beginSlice('webkit.console', 'ActionA', |
| + timer.advanceAndGet(2, 4), ''); |
| + |
| + // Create SurfaceFlinger stats and display rendering stats. |
| + for (var i = 0; i < 10; i++) { |
| + var first = i === 0; |
| + addImplThreadRenderingStats(timer, browser_main, first); |
| + timer.advance(2, 4, 0.4); |
| + } |
| + |
| + for (var i = 0; i < 10; i++) { |
| + var first = i === 0; |
| + addDisplayRenderingStats(timer, browser_main, first, ref_stats); |
| + timer.advance(5, 10, 0.3); |
| + } |
| + |
| + browser_main.sliceGroup.endSlice(timer.advanceAndGet()); |
| + timer.advance(2, 4, 0.9); |
| + |
| + browser.updateBounds(); |
| + renderer.updateBounds(); |
| + var timelineRanges = []; |
| + model.iterateAllEvents(function(event) { |
| + if (event.title === 'ActionA') { |
| + timelineRanges.push(Range.fromExplicitRange(event.start, event.end)); |
| + } |
| + }); |
| + var stats = new RenderingStats(renderer, browser, null, timelineRanges); |
| + |
| + // Compare rendering stats to reference - Only SurfaceFlinger stats should |
| + // count |
| + assert.deepEqual(stats.frameTimestamps, ref_stats.frameTimestamps); |
| + assert.deepEqual(stats.frameTimes, ref_stats.frameTimes); |
| + }); |
| + |
| + |
| + test('rangeWithoutFrames', function() { |
| + var model = new tr.Model(); |
| + var timer = new MockTimer(); |
| + |
| + // Create a renderer process, with a main thread and impl thread. |
| + var renderer = model.getOrCreateProcess(2); |
| + var renderer_main = renderer.getOrCreateThread(21); |
| + var renderer_compositor = renderer.getOrCreateThread(22); |
| + |
| + // Create 10 main and impl rendering stats events for Action A. |
| + renderer_main.sliceGroup.beginSlice('webkit.console', 'ActionA', |
| + timer.advanceAndGet(2, 4), ''); |
| + for (var i = 0; i < 10; i++) { |
| + var first = i === 0; |
| + addImplThreadRenderingStats(timer, renderer_compositor, first); |
| + } |
| + renderer_main.sliceGroup.endSlice(timer.advanceAndGet(2, 4)); |
| + timer.advance(2, 4, 0.7); |
| + |
| + // Create 5 main and impl rendering stats events not within any action. |
| + for (var i = 0; i < 5; i++) { |
| + var first = i === 0; |
| + addImplThreadRenderingStats(timer, renderer_compositor, first); |
| + } |
| + |
| + // Create Action B without any frames. This should trigger |
| + // NotEnoughFramesError when the RenderingStats object is created. |
| + renderer_main.sliceGroup.beginSlice('webkit.console', 'ActionB', |
| + timer.advanceAndGet(2, 4), ''); |
| + |
| + renderer_main.sliceGroup.endSlice(timer.advanceAndGet(2, 4)); |
| + |
| + renderer.updateBounds(); |
| + var timelineRanges = []; |
| + model.iterateAllEvents(function(event) { |
| + if (event.title === 'ActionA' || event.title === 'ActionB') { |
| + timelineRanges.push(Range.fromExplicitRange(event.start, event.end)); |
| + } |
| + }); |
| + var stats = new RenderingStats( |
| + renderer, null, null, timelineRanges); |
| + |
| + assert.equal(0, stats.frameTimestamps[1].length); |
| + }); |
| + |
| + test('fromTimeline', function() { |
| + var model = new tr.Model(); |
| + |
| + // Create a browser process and a renderer process, and a main thread and |
| + // impl thread for each. |
| + var browser = model.getOrCreateProcess(1); |
| + var browser_compositor = browser.getOrCreateThread(12); |
| + var renderer = model.getOrCreateProcess(2); |
| + var renderer_main = renderer.getOrCreateThread(21); |
| + var renderer_compositor = renderer.getOrCreateThread(22); |
| + |
| + var timer = new MockTimer(); |
| + var renderer_ref_stats = new ReferenceRenderingStats(); |
| + var browser_ref_stats = new ReferenceRenderingStats(); |
| + |
| + // Create 10 main and impl rendering stats events for Action A. |
| + renderer_main.sliceGroup.beginSlice('webkit.console', 'ActionA', |
| + timer.advanceAndGet(2, 4), ''); |
| + renderer_ref_stats.pushNewRange(); |
| + browser_ref_stats.pushNewRange(); |
| + for (var i = 0; i < 10; i++) { |
| + var first = i === 0; |
| + addImplThreadRenderingStats( |
| + timer, renderer_compositor, first, renderer_ref_stats); |
| + addImplThreadRenderingStats( |
| + timer, browser_compositor, first, browser_ref_stats); |
| + } |
| + renderer_main.sliceGroup.endSlice(timer.advanceAndGet(2, 4)); |
| + |
| + // Create 5 main and impl rendering stats events not within any action. |
| + for (var i = 0; i < 5; i++) { |
| + var first = i === 0; |
| + addImplThreadRenderingStats(timer, renderer_compositor, first); |
| + addImplThreadRenderingStats(timer, browser_compositor, first); |
| + } |
| + |
| + // Create 10 main and impl rendering stats events for Action B. |
| + renderer_main.sliceGroup.beginSlice('webkit.console', 'ActionB', |
| + timer.advanceAndGet(2, 4), ''); |
| + renderer_ref_stats.pushNewRange(); |
| + browser_ref_stats.pushNewRange(); |
| + for (var i = 0; i < 10; i++) { |
| + var first = i === 0; |
| + addImplThreadRenderingStats( |
| + timer, renderer_compositor, first, renderer_ref_stats); |
| + addImplThreadRenderingStats( |
| + timer, browser_compositor, first, browser_ref_stats); |
| + } |
| + renderer_main.sliceGroup.endSlice(timer.advanceAndGet(2, 4)); |
| + |
| + // Create 10 main and impl rendering stats events for Action A. |
| + renderer_main.sliceGroup.beginSlice('webkit.console', 'ActionA', |
| + timer.advanceAndGet(2, 4), ''); |
| + renderer_ref_stats.pushNewRange(); |
| + browser_ref_stats.pushNewRange(); |
| + for (var i = 0; i < 10; i++) { |
| + var first = i === 0; |
| + addImplThreadRenderingStats( |
| + timer, renderer_compositor, first, renderer_ref_stats); |
| + addImplThreadRenderingStats( |
| + timer, browser_compositor, first, browser_ref_stats); |
| + } |
| + renderer_main.sliceGroup.endSlice(timer.advanceAndGet(2, 4)); |
| + timer.advance(2, 4, 0.1); |
| + |
| + browser.updateBounds(); |
| + renderer.updateBounds(); |
| + var timelineRanges = []; |
| + model.iterateAllEvents(function(event) { |
| + if (event.title === 'ActionA' || event.title === 'ActionB') { |
| + timelineRanges.push(Range.fromExplicitRange(event.start, event.end)); |
| + } |
| + }); |
| + var stats = new RenderingStats( |
| + renderer, browser, null, timelineRanges); |
| + |
| + // Compare rendering stats to reference. |
| + assert.deepEqual(stats.frameTimestamps, |
| + browser_ref_stats.frameTimestamps); |
| + assert.deepEqual(stats.frameTimes, |
| + browser_ref_stats.frameTimes); |
| + assert.deepEqual(stats.approximatedPixelPercentages, |
| + renderer_ref_stats.approximatedPixelPercentages); |
| + assert.deepEqual(stats.checkerboardedPixelPercentages, |
| + renderer_ref_stats.checkerboardedPixelPercentages); |
| + }); |
| + |
| + test('inputLatencyFromTimeline', function() { |
| + var model = new tr.Model(); |
| + |
| + // Create a browser process and a renderer process. |
| + var browser = model.getOrCreateProcess(1); |
| + var browser_main = browser.getOrCreateThread(11); |
| + var renderer = model.getOrCreateProcess(2); |
| + var renderer_main = renderer.getOrCreateThread(21); |
| + |
| + var timer = new MockTimer(); |
| + var ref_latency = new ReferenceInputLatencyStats(); |
| + |
| + // Create 10 input latency stats events for Action A. |
| + renderer_main.sliceGroup.beginSlice('webkit.console', 'ActionA', |
| + timer.advanceAndGet(2, 4), ''); |
| + for (var i = 0; i < 10; i++) { |
| + addInputLatencyStats(timer, browser_main, renderer_main, ref_latency); |
| + } |
| + renderer_main.sliceGroup.endSlice(timer.advanceAndGet(2, 4)); |
| + |
| + // Create 5 input latency stats events not within any action. |
| + timer.advance(2, 4, 0.3); |
| + for (var i = 0; i < 5; i++) { |
| + addInputLatencyStats(timer, browser_main, renderer_main); |
| + } |
| + |
| + // Create 10 input latency stats events for Action B. |
| + renderer_main.sliceGroup.beginSlice('webkit.console', 'ActionB', |
| + timer.advanceAndGet(2, 4), ''); |
| + for (var i = 0; i < 10; i++) { |
| + addInputLatencyStats(timer, browser_main, renderer_main, ref_latency); |
| + } |
| + renderer_main.sliceGroup.endSlice(timer.advanceAndGet(2, 4)); |
| + |
| + // Create 10 input latency stats events for Action A. |
| + renderer_main.sliceGroup.beginSlice('webkit.console', 'ActionA', |
| + timer.advanceAndGet(2, 4), ''); |
| + for (var i = 0; i < 10; i++) { |
| + addInputLatencyStats(timer, browser_main, renderer_main, ref_latency); |
| + } |
| + renderer_main.sliceGroup.endSlice(timer.advanceAndGet(2, 4)); |
| + |
| + browser.updateBounds(); |
| + renderer.updateBounds(); |
| + |
| + var latencyEvents = []; |
| + var timelineRanges = []; |
| + model.iterateAllEvents(function(event) { |
| + if (event.title === 'ActionA' || event.title === 'ActionB') { |
| + var range = Range.fromExplicitRange(event.start, event.end); |
| + timelineRanges.push(range); |
| + latencyEvents.push.apply( |
| + latencyEvents, |
| + RenderingStatsHelpers.getLatencyEvents(browser, range)); |
| + } |
| + }); |
| + assert.deepEqual(latencyEvents, ref_latency.input_event); |
| + |
| + var stats = new RenderingStats(renderer, browser, null, timelineRanges); |
| + |
| + var expected_input_latency = ref_latency.inputEventLatency.filter( |
| + function(e) { |
| + return (e.eventTitle !== |
| + constants.SCROLL_UPDATE_EVENT_NAME); |
| + }).map( |
| + function(e) { |
| + return e.latency; |
| + } |
| + ); |
| + assert.deepEqual( |
| + flattenArray(stats.inputEventLatency), |
| + expected_input_latency); |
| + var expected_scroll_latency = ref_latency.inputEventLatency.filter( |
| + function(e) { |
| + return (e.eventTitle === |
| + constants.SCROLL_UPDATE_EVENT_NAME); |
| + }).map( |
| + function(e) { |
| + return e.latency; |
| + } |
| + ); |
| + |
| + assert.deepEqual( |
| + flattenArray(stats.scrollUpdateLatency), |
| + expected_scroll_latency); |
| + |
| + var expected_gestureScrollUpdateLatency = |
| + ref_latency.inputEventLatency.filter( |
| + function(e) { |
| + return (e.eventTitle === |
| + constants.GESTURE_SCROLL_UPDATE_EVENT_NAME); |
| + }).map( |
| + function(e) { |
| + return e.latency; |
| + } |
| + ); |
| + |
| + assert.deepEqual( |
| + flattenArray(stats.gestureScrollUpdateLatency), |
| + expected_gestureScrollUpdateLatency); |
| + }); |
| + |
| +}); |
| +</script> |