diff options
author | jbates@chromium.org <jbates@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-01-03 21:01:59 +0000 |
---|---|---|
committer | jbates@chromium.org <jbates@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-01-03 21:01:59 +0000 |
commit | 1e42174b401dbf8105ec4e5d864bad1c906c5717 (patch) | |
tree | 97c17cdae120c77b4cb173a2c5f632d5fb4c8476 | |
parent | 1bf0ec47259705707ea7f90182554d49ef50ac39 (diff) | |
download | chromium_src-1e42174b401dbf8105ec4e5d864bad1c906c5717.zip chromium_src-1e42174b401dbf8105ec4e5d864bad1c906c5717.tar.gz chromium_src-1e42174b401dbf8105ec4e5d864bad1c906c5717.tar.bz2 |
Input latency performance test that uses tracing.
This CL adds a new gyp target called performance_browser_tests to run the new input latency test as a browser test.
See the comments in latency_tests.cc for an overview of how latency is measured.
Review URL: http://codereview.chromium.org/8883005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@116191 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | build/all.gyp | 7 | ||||
-rw-r--r-- | chrome/chrome_tests.gypi | 178 | ||||
-rw-r--r-- | chrome/test/data/perf/latency_suite.html | 152 | ||||
-rw-r--r-- | chrome/test/perf/browser_perf_test.cc | 34 | ||||
-rw-r--r-- | chrome/test/perf/browser_perf_test.h | 29 | ||||
-rw-r--r-- | chrome/test/perf/rendering/OWNERS | 3 | ||||
-rw-r--r-- | chrome/test/perf/rendering/latency_tests.cc | 615 | ||||
-rw-r--r-- | content/browser/renderer_host/render_widget_host.cc | 8 | ||||
-rw-r--r-- | gpu/command_buffer/service/gles2_cmd_decoder.cc | 11 |
9 files changed, 1032 insertions, 5 deletions
diff --git a/build/all.gyp b/build/all.gyp index 07fb11e..2a21a48 100644 --- a/build/all.gyp +++ b/build/all.gyp @@ -1,4 +1,4 @@ -# Copyright (c) 2011 The Chromium Authors. All rights reserved. +# Copyright (c) 2012 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. @@ -239,6 +239,7 @@ 'type': 'none', 'dependencies': [ 'chromium_builder_qa', # needed for pyauto + '../chrome/chrome.gyp:performance_browser_tests', '../chrome/chrome.gyp:performance_ui_tests', '../chrome/chrome.gyp:plugin_tests', '../chrome/chrome.gyp:sync_performance_tests', @@ -250,6 +251,7 @@ 'type': 'none', 'dependencies': [ '../chrome/chrome.gyp:gpu_tests', + '../chrome/chrome.gyp:performance_browser_tests', '../chrome/chrome.gyp:performance_ui_tests', '../third_party/WebKit/Source/WebKit/chromium/WebKit.gyp:DumpRenderTree', ], @@ -315,6 +317,7 @@ 'type': 'none', 'dependencies': [ '../chrome/chrome.gyp:browser_tests', + '../chrome/chrome.gyp:performance_browser_tests', '../chrome/chrome.gyp:performance_ui_tests', '../chrome/chrome.gyp:plugin_tests', '../chrome/chrome.gyp:safe_browsing_tests', @@ -392,6 +395,7 @@ '../chrome/chrome.gyp:installer_util_unittests', '../chrome/chrome.gyp:interactive_ui_tests', '../chrome/chrome.gyp:mini_installer_test', + '../chrome/chrome.gyp:performance_browser_tests', '../chrome/chrome.gyp:performance_ui_tests', '../chrome/chrome.gyp:plugin_tests', '../chrome/chrome.gyp:safe_browsing_tests', @@ -531,6 +535,7 @@ '../chrome/chrome.gyp:browser_tests', '../chrome/chrome.gyp:chrome', '../chrome/chrome.gyp:interactive_ui_tests', + '../chrome/chrome.gyp:performance_browser_tests', '../chrome/chrome.gyp:performance_ui_tests', '../chrome/chrome.gyp:safe_browsing_tests', '../chrome/chrome.gyp:sync_unit_tests', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 04b5feb..188a834 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -3051,6 +3051,184 @@ ], # conditions }, # target browser_tests { + # Executable that runs each perf browser test in a new process. + 'target_name': 'performance_browser_tests', + 'type': 'executable', + 'msvs_cygwin_shell': 0, + 'msvs_cygwin_dirs': ['<(DEPTH)/third_party/cygwin'], + 'dependencies': [ + 'browser', + 'browser/sync/protocol/sync_proto.gyp:sync_proto', + 'chrome', + 'chrome_resources.gyp:chrome_resources', + 'chrome_resources.gyp:chrome_strings', + 'renderer', + 'test_support_common', + '../base/base.gyp:base', + '../base/base.gyp:base_i18n', + '../base/base.gyp:test_support_base', + '../net/net.gyp:net', + '../net/net.gyp:net_test_support', + '../skia/skia.gyp:skia', + '../testing/gmock.gyp:gmock', + '../testing/gtest.gyp:gtest', + '../third_party/cld/cld.gyp:cld', + '../third_party/icu/icu.gyp:icui18n', + '../third_party/icu/icu.gyp:icuuc', + '../third_party/leveldatabase/leveldatabase.gyp:leveldatabase', + '../v8/tools/gyp/v8.gyp:v8', + '../webkit/webkit.gyp:test_shell_test_support', + # Runtime dependencies + '../third_party/mesa/mesa.gyp:osmesa', + '../third_party/WebKit/Source/WebKit/chromium/WebKit.gyp:copy_TestNetscapePlugIn', + ], + 'include_dirs': [ + '..', + ], + 'defines': [ + 'HAS_OUT_OF_PROC_TEST_RUNNER', + 'BROWSER_TESTS_HEADER_OVERRIDE="chrome/test/base/in_process_browser_test.h"', + ], + 'sources': [ + 'app/breakpad_mac_stubs.mm', + 'app/chrome_command_ids.h', + 'app/chrome_dll.rc', + 'app/chrome_dll_resource.h', + 'app/chrome_version.rc.version', + 'test/base/chrome_render_view_test.cc', + 'test/base/chrome_render_view_test.h', + 'test/base/chrome_test_launcher.cc', + 'test/perf/browser_perf_test.cc', + 'test/perf/browser_perf_test.h', + 'test/perf/rendering/latency_tests.cc', + '../content/test/test_launcher.cc', + '../content/test/test_launcher.h', + ], + 'rules': [ + { + 'rule_name': 'js2webui', + 'extension': 'js', + 'msvs_external_rule': 1, + 'inputs': [ + '<(gypv8sh)', + '<(PRODUCT_DIR)/v8_shell<(EXECUTABLE_SUFFIX)', + '<(mock_js)', + '<(test_api_js)', + '<(js2gtest)', + ], + 'outputs': [ + '<(INTERMEDIATE_DIR)/chrome/<(RULE_INPUT_DIRNAME)/<(RULE_INPUT_ROOT)-gen.cc', + '<(PRODUCT_DIR)/test_data/chrome/<(RULE_INPUT_DIRNAME)/<(RULE_INPUT_ROOT).js', + ], + 'process_outputs_as_sources': 1, + 'action': [ + 'python', + '<@(_inputs)', + 'webui', + '<(RULE_INPUT_PATH)', + 'chrome/<(RULE_INPUT_DIRNAME)/<(RULE_INPUT_ROOT).js', + '<@(_outputs)', + ], + }, + ], + 'conditions': [ + ['OS!="linux" or toolkit_views==1', { + 'sources!': [ + 'browser/extensions/browser_action_test_util_gtk.cc', + ], + }], + ['OS=="win"', { + 'sources': [ + '<(SHARED_INTERMEDIATE_DIR)/chrome/browser_resources.rc', + '<(SHARED_INTERMEDIATE_DIR)/chrome/common_resources.rc', + '<(SHARED_INTERMEDIATE_DIR)/chrome/renderer_resources.rc', + '<(SHARED_INTERMEDIATE_DIR)/chrome/theme_resources.rc', + '<(SHARED_INTERMEDIATE_DIR)/chrome/theme_resources_standard.rc', + '<(SHARED_INTERMEDIATE_DIR)/chrome_version/other_version.rc', + '<(SHARED_INTERMEDIATE_DIR)/net/net_resources.rc', + '<(SHARED_INTERMEDIATE_DIR)/ui/ui_resources/ui_resources.rc', + '<(SHARED_INTERMEDIATE_DIR)/ui/ui_resources_standard/ui_resources_standard.rc', + '<(SHARED_INTERMEDIATE_DIR)/webkit/webkit_chromium_resources.rc', + '<(SHARED_INTERMEDIATE_DIR)/webkit/webkit_resources.rc', + ], + 'include_dirs': [ + '<(DEPTH)/third_party/wtl/include', + ], + 'dependencies': [ + 'chrome_version_resources', + 'installer_util_strings', + '../sandbox/sandbox.gyp:sandbox', + ], + 'conditions': [ + ['win_use_allocator_shim==1', { + 'dependencies': [ + '<(allocator_target)', + ], + }], + ], + 'configurations': { + 'Debug_Base': { + 'msvs_settings': { + 'VCLinkerTool': { + 'LinkIncremental': '<(msvs_debug_link_nonincremental)', + }, + }, + }, + } + }, { # else: OS != "win" + 'sources!': [ + 'app/chrome_command_ids.h', + 'app/chrome_dll.rc', + 'app/chrome_dll_resource.h', + 'app/chrome_version.rc.version', + ], + }], + ['toolkit_uses_gtk == 1', { + 'dependencies': [ + '../build/linux/system.gyp:gtk', + '../tools/xdisplaycheck/xdisplaycheck.gyp:xdisplaycheck', + ], + }], + ['toolkit_uses_gtk == 1 or chromeos==1 or (OS=="linux" and use_aura==1)', { + 'dependencies': [ + '../build/linux/system.gyp:ssl', + ], + }], + ['OS=="mac"', { + 'include_dirs': [ + '../third_party/GTM', + ], + # TODO(mark): We really want this for all non-static library + # targets, but when we tried to pull it up to the common.gypi + # level, it broke other things like the ui, startup, and + # page_cycler tests. *shrug* + 'xcode_settings': { + 'OTHER_LDFLAGS': [ + '-Wl,-ObjC', + ], + }, + # See the comment in this section of the unit_tests target for an + # explanation (crbug.com/43791 - libwebcore.a is too large to mmap). + 'dependencies+++': [ + '../third_party/WebKit/Source/WebCore/WebCore.gyp/WebCore.gyp:webcore', + ], + }, { # else: OS != "mac" + 'sources!': [ + 'browser/extensions/browser_action_test_util_mac.mm', + ], + }], + ['os_posix == 1 and OS != "mac"', { + 'conditions': [ + ['linux_use_tcmalloc==1', { + 'dependencies': [ + '../base/allocator/allocator.gyp:allocator', + ], + }], + ], + }], + ], # conditions + }, # target performance_browser_tests + { # Executable that runs safebrowsing test in a new process. 'target_name': 'safe_browsing_tests', 'type': 'executable', diff --git a/chrome/test/data/perf/latency_suite.html b/chrome/test/data/perf/latency_suite.html new file mode 100644 index 0000000..a05ea82 --- /dev/null +++ b/chrome/test/data/perf/latency_suite.html @@ -0,0 +1,152 @@ +<html> +<head> +<script type="text/javascript"> +/** + * @fileoverview This page executes various animation behaviors based on URL + * arguments to test input latency. There are two main modes of + * operation: webgl and software. Both modes use + * requestAnimationFrame to drive the update rate. The basic task + * of the page is to collect mouse input coordinates in the input + * handler and render with the latest input coordinate in RAF. The + * latency test will look at the rendering trace data to detect + * the latest mouse coordinate that affected the frame. For + * software runs, the pixel at 0,0 on the page must contain the + * mouse coordinate encoded as a color. + */ + +var frameCountWarmup = 5; +var frameCount = 0; +var gl = null; +var mouseX = 0; +var testParams = {}; + +function parseParams() { + var query = window.location.search.substring(1); + if (!query) + return; + var params = query.split('&'); + for (var i = 0, len = params.length; i < len; i++) { + var pair = params[i].split('='); + if (pair.length == 1) + testParams[pair[0]] = true; + else + testParams[pair[0]] = pair[1]; + } +} + +function setCoordinates(e) { + // Ignore mouse events with wrong Y coordinate. + if (e.clientY != parseInt(testParams.y)) + return; + + mouseX = e.clientX; + if (testParams.inputDirty) { + document.getElementById('text').firstChild.textContent = + mouseX.toString(); + } + if (testParams.inputHeavy) { + sleep(parseInt(testParams.delayTimeMS)); + } +} + +function init() { + parseParams(); + + if (testParams.mode == 'webgl') { + var canvas = document.getElementById('canvas'); + if (!canvas) + return false; + canvas.width = parseInt(testParams.canvasWidth); + canvas.height = parseInt(testParams.canvasWidth); + try { + // Specify antialiasing to ensure that we get a BlitFramebufferEXT in + // the trace when the compositor consumes a webgl frame. + gl = canvas.getContext('webgl', { antialias: true }); + } catch (e) {} + if (!gl) { + try { + gl = canvas.getContext('experimental-webgl'); + } catch (e) { + return false; + } + } + return true; + } else if (testParams.mode == 'software') { + var table = document.getElementById('table'); + table.style.backgroundColor = '#ff00ff'; + return true; + } +} + +function onLoad() { + if (init()) + window.webkitRequestAnimationFrame(draw); + else + endTest(); +} + +function sleep(milliseconds) { + var start = Date.now(); + while(Date.now() - start <= milliseconds); +} + +function draw() { + if (testParams.rafHeavy) { + sleep(parseInt(testParams.delayTimeMS)); + } + + if (testParams.mode == 'webgl') { + gl.viewport(0, 0, testParams.canvasWidth, testParams.canvasWidth); + if (testParams.paintHeavy) { + gl.clearColor(0, 0, 0.0, 1.0); + for (var i = 0; i < 1000; ++i) + gl.clear(gl.COLOR_BUFFER_BIT); + } + gl.clearColor(mouseX, testParams.clearColorGreen, 0.0, 1.0); + gl.clear(gl.COLOR_BUFFER_BIT); + } else if (testParams.mode == 'software') { + var table = document.getElementById('table'); + // Encode mouse x value into color channels (support up to 64k x values). + var g = (mouseX & 0xff00) >> 8; + var b = (mouseX & 0xff); + table.style.backgroundColor = 'rgb(0, ' + g + ', ' + b + ')'; + // When no inputs are coming in, the first table won't change. Since we + // still need to cause a paint, toggle the color of another element: + var table2 = document.getElementById('table2'); + table2.style.backgroundColor = (frameCount & 1) ? 'gray' : 'silver'; + if (testParams.paintHeavy) { + var body = document.getElementById('body'); + body.style.backgroundColor = (frameCount & 1) ? 'silver' : 'gray'; + } + } + + frameCount++; + if (frameCount == parseInt(testParams.numFrames)) { + if (testParams.mode == 'webgl') + gl.finish(); + endTest(); + } else { + window.webkitRequestAnimationFrame(draw); + } +} + +function endTest() { + domAutomationController.setAutomationId(1); + domAutomationController.send('FINISHED'); +} +</script> +</head> +<style> +#table { + height: 10px; + width: 10px; +} +</style> +<body id="body" style="margin:0px" onload="onLoad()" + onmousemove="setCoordinates(event)"> +<table id="table"><tr/></table> +<table id="table2"><tr/></table> +<canvas id="canvas"></canvas> +<p><b id="text">x</b></p> +</body> +</html> diff --git a/chrome/test/perf/browser_perf_test.cc b/chrome/test/perf/browser_perf_test.cc new file mode 100644 index 0000000..30d54a8 --- /dev/null +++ b/chrome/test/perf/browser_perf_test.cc @@ -0,0 +1,34 @@ +// Copyright (c) 2012 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. + +#include "chrome/test/perf/browser_perf_test.h" + +#include "base/command_line.h" +#include "base/process_util.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/test/base/chrome_process_util.h" +#include "chrome/test/perf/perf_test.h" + +BrowserPerfTest::BrowserPerfTest() { +} + +BrowserPerfTest::~BrowserPerfTest() { +} + +void BrowserPerfTest::SetUpCommandLine(CommandLine* command_line) { + // Reduce performance test variance by disabling background networking. + command_line->AppendSwitch(switches::kDisableBackgroundNetworking); +} + +void BrowserPerfTest::PrintIOPerfInfo(const std::string& test_name) { + base::ProcessId browser_pid = base::GetCurrentProcId(); + ChromeProcessList chrome_processes(GetRunningChromeProcesses(browser_pid)); + perf_test::PrintIOPerfInfo(test_name, chrome_processes, browser_pid); +} + +void BrowserPerfTest::PrintMemoryUsageInfo(const std::string& test_name) { + base::ProcessId browser_pid = base::GetCurrentProcId(); + ChromeProcessList chrome_processes(GetRunningChromeProcesses(browser_pid)); + perf_test::PrintMemoryUsageInfo(test_name, chrome_processes, browser_pid); +} diff --git a/chrome/test/perf/browser_perf_test.h b/chrome/test/perf/browser_perf_test.h new file mode 100644 index 0000000..3310345 --- /dev/null +++ b/chrome/test/perf/browser_perf_test.h @@ -0,0 +1,29 @@ +// Copyright (c) 2012 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. + +#ifndef CHROME_TEST_PERF_BROWSER_PERF_TEST_H_ +#define CHROME_TEST_PERF_BROWSER_PERF_TEST_H_ +#pragma once + +#include "chrome/test/base/in_process_browser_test.h" + +class CommandLine; + +class BrowserPerfTest : public InProcessBrowserTest { + public: + BrowserPerfTest(); + virtual ~BrowserPerfTest(); + + // Set up common browser perf test flags. Typically call down to this if + // overridden. + virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE; + + // Prints IO performance data for use by perf graphs. + void PrintIOPerfInfo(const std::string& test_name); + + // Prints memory usage data for use by perf graphs. + void PrintMemoryUsageInfo(const std::string& test_name); +}; + +#endif // CHROME_TEST_PERF_BROWSER_PERF_TEST_H_ diff --git a/chrome/test/perf/rendering/OWNERS b/chrome/test/perf/rendering/OWNERS new file mode 100644 index 0000000..209a23a --- /dev/null +++ b/chrome/test/perf/rendering/OWNERS @@ -0,0 +1,3 @@ +nduca@chromium.org +junov@chromium.org +jbates@chromium.org diff --git a/chrome/test/perf/rendering/latency_tests.cc b/chrome/test/perf/rendering/latency_tests.cc new file mode 100644 index 0000000..576b2d8 --- /dev/null +++ b/chrome/test/perf/rendering/latency_tests.cc @@ -0,0 +1,615 @@ +// Copyright (c) 2012 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. + +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/path_service.h" +#include "base/string_number_conversions.h" +#include "base/stringprintf.h" +#include "base/test/test_switches.h" +#include "base/test/trace_event_analyzer.h" +#include "base/threading/platform_thread.h" +#include "base/timer.h" +#include "base/version.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/test/base/tracing.h" +#include "chrome/test/base/ui_test_utils.h" +#include "chrome/test/perf/browser_perf_test.h" +#include "chrome/test/perf/perf_test.h" +#include "content/browser/renderer_host/render_view_host.h" +#include "content/browser/tab_contents/tab_contents.h" +#include "content/public/common/content_switches.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h" +#include "testing/gtest/include/gtest/gtest.h" + +// Run with --vmodule=latency_tests=1 to print verbose latency info. + +// How is latency measured? +// +// The test injects mouse moves many times per frame from the browser via +// RenderWidgetHost. Each input has a unique x coordinate. When the javascript +// handler receives the input, it stores the coordinate for later use in the +// requestAnimationFrame callback. In RAF, the test paints using the x +// coordinate as a color (in software, it sets the color of a table; in webgl, +// it executes a glClearColor). Trace events emit the color when it is picked up +// by either UpdateRect for software or gles2_cmd_decoder/glClear for webgl. +// +// Each UpdateRect (software) or SwapBuffers (webgl) is considered to be a frame +// boundary that will be used to measure latency in number of frames. Starting +// from a frame boundary Y, the test first determines what mouse x coordinate +// was represented by the color at that frame boundary. Then, the test walks +// backward through the trace events to find the input event matching that +// x coordinate. Then, the test find the nearest frame boundary X to the input +// event (may be before or after). The number of frame boundaries is then +// counted between X and Y to determine the input latency. +// +// By injecting mouse moves many times per frame, we reduce flakiness in the +// finding of the nearest frame boundary. +// +// This test only measures the latency introduced by chrome code -- it does not +// measure latency introduced by mouse drivers or the GL driver or the OS window +// manager. The actual latency seen by a user is more than what is reported by +// this test. +// +// Current modes: +// - Software RAF +// - WebGL RAF + +namespace { + +using namespace trace_analyzer; + +enum LatencyTestMode { + kWebGL, + kSoftware +}; + +enum LatencyTestFlags { + kInputHeavy = 1 << 0, + kInputDirty = 1 << 1, + kRafHeavy = 1 << 2, + kPaintHeavy = 1 << 3 +}; + +const int kWebGLCanvasWidth = 10; +const int kNumFrames = 80; +const int kInputsPerFrame = 16; +// Magic number to identify certain glClear events. +const int kClearColorGreen = 137; +const int kMouseY = 5; + +// Don't analyze begin frames that may be inaccurate. Latencies can be as high +// as 5 frames or so, so skip the first 6 frames to get more accurate results. +const int kIgnoreBeginFrames = 6; +// Don't analyze end frames that may be inaccurate. +const int kIgnoreEndFrames = 4; +// Minimum frames to produce an answer. +const int kMinimumFramesForAnalysis = 5; + +class LatencyTest + : public BrowserPerfTest, + public ::testing::WithParamInterface<int> { + public: + LatencyTest() : + query_instant_(Query(EVENT_PHASE) == + Query::Phase(TRACE_EVENT_PHASE_INSTANT)), + // These queries are initialized in RunTest. + query_swaps_(Query::Bool(false)), + query_inputs_(Query::Bool(false)), + query_blits_(Query::Bool(false)), + query_clears_(Query::Bool(false)), + mouse_x_(0), + tab_width_(0), + mode_(kWebGL), + delay_time_us_(0), + num_frames_(0), + verbose_(false), + test_flags_(0) {} + + virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE; + + std::vector<int> GetAllBehaviors(); + + // Run test with specified |mode| and |behaviors|. + // |mode| can be webgl or software. + // |behaviors| is a list of combinations of LatencyTestFlags. + void RunTest(LatencyTestMode mode, const std::vector<int>& behaviors); + + private: + void RunTestInternal(const std::string& test_url, + bool send_inputs, + int input_delay_us); + + double CalculateLatency(); + + std::string GetModeString() { + switch (mode_) { + case kWebGL: + return "webgl"; + case kSoftware: + return "software"; + default: + NOTREACHED() << "invalid mode"; + return ""; + } + } + + std::string GetTraceName(int flags); + + std::string GetUrlModeString(int flags); + + std::string GetUrl(int flags); + + void GetMeanFrameTimeMicros(int* frame_time) const; + + void SendInput(); + + void PrintEvents(const TraceEventVector& events); + + // Path to html file. + FilePath test_path_; + + // Query INSTANT events. + Query query_instant_; + + // Query "swaps" which is SwapBuffers for GL and UpdateRect for software. + Query query_swaps_; + + // Query mouse input entry events in browser process (ForwardMouseEvent). + Query query_inputs_; + + // Query GL blits for the WebGL canvas -- represents the compositor consuming + // the WebGL contents for display. + Query query_blits_; + + // Query glClear calls with mouse coordinate as clear color. + Query query_clears_; + + // For searching trace data. + scoped_ptr<TraceAnalyzer> analyzer_; + + // Current mouse x coordinate for injecting events. + int mouse_x_; + + // Width of window containing our tab. + int tab_width_; + + // Timer for injecting mouse events periodically. + base::RepeatingTimer<LatencyTest> timer_; + + // Mode: webgl or software. + LatencyTestMode mode_; + + // Delay time for javascript test code. Typically 2 x frame duration. Used + // to spin-wait in the javascript input handler and requestAnimationFrame. + int delay_time_us_; + + // Number of frames to render from the html test code. + int num_frames_; + + // Map from test flags combination to the calculated mean latency. + std::map<int, double> latencies_; + + // Whether to print more verbose output. + bool verbose_; + + // Current test flags combination, determining the behavior of the test. + int test_flags_; +}; + +void LatencyTest::SetUpCommandLine(CommandLine* command_line) { + BrowserPerfTest::SetUpCommandLine(command_line); + // This enables DOM automation for tab contents. + EnableDOMAutomation(); + if (CommandLine::ForCurrentProcess()-> + HasSwitch(switches::kEnableThreadedCompositing)) { + command_line->AppendSwitch(switches::kEnableThreadedCompositing); + } + // Default behavior is to thumbnail the tab after 0.5 seconds, causing + // a nasty frame hitch and disturbing the latency test. Fix that: + command_line->AppendSwitch(switches::kEnableInBrowserThumbnailing); + command_line->AppendSwitch(switches::kDisableBackgroundNetworking); +} + +std::vector<int> LatencyTest::GetAllBehaviors() { + std::vector<int> behaviors; + int max_behaviors = kInputHeavy | kInputDirty | kRafHeavy | kPaintHeavy; + for (int i = 0; i <= max_behaviors; ++i) + behaviors.push_back(i); + return behaviors; +} + +void LatencyTest::RunTest(LatencyTestMode mode, + const std::vector<int>& behaviors) { + mode_ = mode; + verbose_ = (logging::GetVlogLevel("latency_tests") > 0); + + // Construct queries for searching trace events via TraceAnalyzer. + if (mode_ == kWebGL) { + query_swaps_ = query_instant_ && + Query(EVENT_NAME) == Query::String("SwapBuffers") && + Query(EVENT_ARG, "width") != Query::Int(kWebGLCanvasWidth); + } else if (mode_ == kSoftware) { + // Software updates need to have x=0 and y=0 to contain the input color. + query_swaps_ = query_instant_ && + Query(EVENT_NAME) == Query::String("UpdateRect") && + Query(EVENT_ARG, "x+y") == Query::Int(0); + } + query_inputs_ = query_instant_ && + Query(EVENT_NAME) == Query::String("MouseEventBegin"); + query_blits_ = query_instant_ && + Query(EVENT_NAME) == Query::String("DoBlit") && + Query(EVENT_ARG, "width") == Query::Int(kWebGLCanvasWidth); + query_clears_ = query_instant_ && + Query(EVENT_NAME) == Query::String("DoClear") && + Query(EVENT_ARG, "green") == Query::Int(kClearColorGreen); + Query query_width_swaps = query_swaps_; + if (mode_ == kSoftware) { + query_width_swaps = query_instant_ && + Query(EVENT_NAME) == Query::String("UpdateRectWidth") && + Query(EVENT_ARG, "width") > Query::Int(kWebGLCanvasWidth); + } + + // Set path to test html. + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_path_)); + test_path_ = test_path_.Append(FILE_PATH_LITERAL("perf")); + test_path_ = test_path_.Append(FILE_PATH_LITERAL("latency_suite.html")); + ASSERT_TRUE(file_util::PathExists(test_path_)) + << "Missing test file: " << test_path_.value(); + + // Run once with defaults to measure the frame times. + delay_time_us_ = 0; + // kNumFrames may be very high, but we only need a few frames to measure + // average frame times. + num_frames_ = 30; + int initial_flags = 0; + if (mode_ == kSoftware) { + // For the first run, run software with kPaintHeavy (which toggles the + // background color every frame) to force an update each RAF. Otherwise it + // won't trigger an UpdateRect each frame and we won't be able to measure + // framerate, because there are no inputs during the first run. + initial_flags = static_cast<int>(kPaintHeavy); + } + RunTestInternal(GetUrl(initial_flags), false, 0); + + // Get width of tab so that we know the limit of x coordinates for the + // injected mouse inputs. + const TraceEvent* swap_event = analyzer_->FindOneEvent(query_width_swaps); + ASSERT_TRUE(swap_event); + tab_width_ = swap_event->GetKnownArgAsInt("width"); + // Keep printf output clean by limiting input coords to three digits: + tab_width_ = (tab_width_ < 1000) ? tab_width_ : 999; + // Sanity check the tab_width -- it should be more than 100 pixels. + EXPECT_GT(tab_width_, 100); + + int mean_frame_time_us = 0; + GetMeanFrameTimeMicros(&mean_frame_time_us); + if (verbose_) + printf("Mean frame time micros = %d\n", mean_frame_time_us); + // Delay time is 2x the average frame time. + delay_time_us_ = 2 * mean_frame_time_us; + // Calculate delay time between inputs based on the measured frame time. + // This prevents flooding the browser with more events than we need if the + // test is running very slow (such as on a VM). + int delay_us = mean_frame_time_us / kInputsPerFrame; + + // Reset num_frames_ for actual test runs. + num_frames_ = kNumFrames; + + // Run input latency test with each requested behavior. + for (size_t i = 0; i < behaviors.size(); ++i) { + test_flags_ = behaviors[i]; + std::string url = GetUrl(test_flags_); + printf("=============================================================\n"); + if (verbose_) + printf("Mode: %s\n", GetUrlModeString(i).c_str()); + printf("URL: %s\n", url.c_str()); + + // Do the actual test with input events. + RunTestInternal(url, true, delay_us); + latencies_[test_flags_] = CalculateLatency(); + } + + // Print summary if more than 1 behavior was tested in this run. This is only + // for manual test runs for human reabable results, not for perf bots. + if (behaviors.size() > 1) { + printf("#############################################################\n"); + printf("## %s\n", GetModeString().c_str()); + if (verbose_) { + printf("Latency, behavior:\n"); + for (size_t i = 0; i < behaviors.size(); ++i) { + printf("%.1f, %s%s%s%s\n", latencies_[behaviors[i]], + (i & kInputHeavy) ? "InputHeavy " : "", + (i & kInputDirty) ? "InputDirty " : "", + (i & kRafHeavy) ? "RafHeavy " : "", + (i & kPaintHeavy) ? "PaintHeavy " : ""); + } + } + printf("Latencies for tests: "); + for (size_t i = 0; i < behaviors.size(); ++i) { + printf("%.1f%s", latencies_[behaviors[i]], + (i < behaviors.size() - 1) ? ", " : ""); + } + printf("\n"); + printf("#############################################################\n"); + } +} + +void LatencyTest::RunTestInternal(const std::string& test_url, + bool send_inputs, + int input_delay_us) { + mouse_x_ = 0; + + ASSERT_TRUE(tracing::BeginTracing("test_gpu,test_latency")); + + ui_test_utils::NavigateToURLWithDisposition( + browser(), GURL(test_url), CURRENT_TAB, + ui_test_utils::BROWSER_TEST_NONE); + + // Start sending mouse inputs. + if (send_inputs) { + // Round input_delay_us down to nearest milliseconds. The rounding in timer + // code rounds up from us to ms, so we need to do our own rounding here. + int input_delay_ms = input_delay_us / 1000; + input_delay_ms = (input_delay_ms <= 0) ? 1 : input_delay_ms; + timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(input_delay_ms), + this, &LatencyTest::SendInput); + } + + // Wait for message indicating the test has finished running. + ui_test_utils::DOMMessageQueue message_queue; + ASSERT_TRUE(message_queue.WaitForMessage(NULL)); + + timer_.Stop(); + + std::string json_events; + ASSERT_TRUE(tracing::EndTracing(&json_events)); + + analyzer_.reset(TraceAnalyzer::Create(json_events)); + analyzer_->AssociateBeginEndEvents(); + analyzer_->MergeAssociatedEventArgs(); +} + +double LatencyTest::CalculateLatency() { + TraceEventVector events; + if (mode_ == kWebGL) { + // Search for three types of events in WebGL mode: + // - onscreen swaps. + // - DoClear calls that contain the mouse x coordinate. + // - mouse events. + analyzer_->FindEvents(query_swaps_ || query_inputs_ || + query_blits_ || query_clears_, &events); + } else if (mode_ == kSoftware) { + analyzer_->FindEvents(query_swaps_ || query_inputs_, &events); + } else { + NOTREACHED() << "invalid mode"; + } + + if (verbose_) + PrintEvents(events); + + int swap_count = 0; + size_t previous_blit_pos = 0; + swap_count = 0; + std::vector<int> latencies; + printf("Measured latency (in number of frames) for each frame:\n"); + for (size_t i = 0; i < events.size(); ++i) { + if (query_swaps_.Evaluate(*events[i])) { + // Compositor context swap buffers. + ++swap_count; + // Don't analyze first few swaps, because they are filling the rendering + // pipeline and may be unstable. + if (swap_count > kIgnoreBeginFrames) { + int mouse_x = 0; + if (mode_ == kWebGL) { + // Trace backwards through the events to find the input event that + // matches the glClear that was presented by this SwapBuffers. + + // Step 1: Find the last blit (which will be the WebGL blit). + size_t blit_pos = 0; + EXPECT_TRUE(FindLastOf(events, query_blits_, i, &blit_pos)); + // Skip this SwapBuffers if the blit has already been consumed by a + // previous SwapBuffers. This means the current frame did not receive + // an update from WebGL. + EXPECT_GT(blit_pos, previous_blit_pos); + if (blit_pos == previous_blit_pos) { + if (verbose_) + printf(" %03d: ERROR\n", swap_count); + else + printf(" ERROR"); + continue; + } + previous_blit_pos = blit_pos; + + // Step 2: find the last clear from the WebGL blit. This will be the + // value of the latest mouse input that has affected this swap. + size_t clear_pos = 0; + EXPECT_TRUE(FindLastOf(events, query_clears_, blit_pos, &clear_pos)); + mouse_x = events[clear_pos]->GetKnownArgAsInt("red"); + } else if (mode_ == kSoftware) { + // The software path gets the mouse_x directly from the DIB colors. + mouse_x = events[i]->GetKnownArgAsInt("color"); + } + + // Find the corresponding mouse input. + size_t input_pos = 0; + Query query_mouse_event = query_inputs_ && + Query(EVENT_ARG, "x") == Query::Int(mouse_x); + EXPECT_TRUE(FindLastOf(events, query_mouse_event, i, &input_pos)); + + // Step 4: Find the nearest onscreen SwapBuffers to this input event. + size_t closest_swap = 0; + size_t second_closest_swap = 0; + EXPECT_TRUE(FindClosest(events, query_swaps_, input_pos, + &closest_swap, &second_closest_swap)); + int latency = CountMatches(events, query_swaps_, closest_swap, i); + latencies.push_back(latency); + if (verbose_) + printf(" %03d: %d\n", swap_count, latency); + else + printf(" %d", latency); + } + } + } + printf("\n"); + + size_t ignoreEndFrames = static_cast<size_t>(kIgnoreEndFrames); + bool haveEnoughFrames = latencies.size() > + ignoreEndFrames + static_cast<size_t>(kMinimumFramesForAnalysis); + EXPECT_TRUE(haveEnoughFrames); + if (!haveEnoughFrames) + return 0.0; + + double mean_latency = 0.0; + // Skip last few frames, because they may be unreliable. + size_t num_consider = latencies.size() - ignoreEndFrames; + for (size_t i = 0; i < num_consider; ++i) + mean_latency += static_cast<double>(latencies[i]); + mean_latency /= static_cast<double>(num_consider); + printf("Mean latency = %f\n", mean_latency); + + double mean_error = 0.0; + for (size_t i = 0; i < num_consider; ++i) { + double offset = fabs(mean_latency - static_cast<double>(latencies[i])); + mean_error = (offset > mean_error) ? offset : mean_error; + } + + std::string trace_name = GetTraceName(test_flags_); + std::string mean_and_error = base::StringPrintf("%f,%f", mean_latency, + mean_error); + perf_test::PrintResultMeanAndError(GetModeString(), "", trace_name, + mean_and_error, "frames", true); + return mean_latency; +} + +std::string LatencyTest::GetTraceName(int flags) { + if (flags == 0) + return "simple"; + std::string name; + if (flags & kInputHeavy) + name += "ih"; + if (flags & kInputDirty) + name += std::string(name.empty()? "" : "_") + "id"; + if (flags & kRafHeavy) + name += std::string(name.empty()? "" : "_") + "rh"; + if (flags & kPaintHeavy) + name += std::string(name.empty()? "" : "_") + "ph"; + return name; +} + +std::string LatencyTest::GetUrlModeString(int flags) { + std::string mode = "&mode=" + GetModeString(); + if (flags & kInputHeavy) + mode += "&inputHeavy"; + if (flags & kInputDirty) + mode += "&inputDirty"; + if (flags & kRafHeavy) + mode += "&rafHeavy"; + if (flags & kPaintHeavy) + mode += "&paintHeavy"; + return mode; +} + +std::string LatencyTest::GetUrl(int flags) { + std::string test_url = + net::FilePathToFileURL(test_path_).possibly_invalid_spec(); + test_url += "?numFrames=" + base::IntToString(num_frames_); + test_url += "&canvasWidth=" + base::IntToString(kWebGLCanvasWidth); + test_url += "&clearColorGreen=" + base::IntToString(kClearColorGreen); + test_url += "&delayTimeMS=" + base::IntToString(delay_time_us_ / 1000); + test_url += "&y=" + base::IntToString(kMouseY); + return test_url + GetUrlModeString(flags); +} + +void LatencyTest::GetMeanFrameTimeMicros(int* frame_time) const { + TraceEventVector events; + // Search for compositor swaps (or UpdateRects in the software path). + analyzer_->FindEvents(query_swaps_, &events); + RateStats stats; + ASSERT_TRUE(GetRateStats(events, &stats)); + + // Check that the number of swaps is close to kNumFrames. + EXPECT_LT(num_frames_ - num_frames_ / 4, static_cast<int>(events.size())); + *frame_time = static_cast<int>(stats.mean_us); +} + +void LatencyTest::SendInput() { + RenderViewHost* rvh = browser()->GetSelectedTabContentsWrapper()-> + tab_contents()->GetRenderManagerForTesting()->current_host(); + WebKit::WebMouseEvent mouse_event; + mouse_event.movementX = 1; + mouse_x_ += mouse_event.movementX; + // Wrap mouse_x_ when it's near the edge of the tab. + if (mouse_x_ > tab_width_ - 5) + mouse_x_ = 1; + mouse_event.x = mouse_event.windowX = mouse_x_; + // Set y coordinate to be a few pixels down from the top of the window, + // so that it is between the top and bottom of the canvas. + mouse_event.y = mouse_event.windowY = 5; + mouse_event.type = WebKit::WebInputEvent::MouseMove; + TRACE_EVENT_INSTANT1("test_latency", "MouseEventBegin", "x", mouse_x_); + rvh->ForwardMouseEvent(mouse_event); +} + +void LatencyTest::PrintEvents(const TraceEventVector& events) { + bool is_software = (mode_ == kSoftware); + int swap_count = 0; + for (size_t i = 0; i < events.size(); ++i) { + if (events[i]->name == "MouseEventBegin") { + printf("%03d ", events[i]->GetKnownArgAsInt("x")); + } else if (events[i]->name == "DoClear") { + printf("Clr%03d ", events[i]->GetKnownArgAsInt("red")); + } else if (events[i]->name == "DoBlit") { + // WebGL context swap buffers. + printf("BLT "); + } else if (events[i]->name == "SwapBuffers") { + // Compositor context swap buffers. + ++swap_count; + printf("|\nframe %03d: ", swap_count + 1); + } else if (is_software && events[i]->name == "UpdateRect") { + ++swap_count; + printf("(%d)|\nframe %03d: ", + events[i]->GetKnownArgAsInt("color"), swap_count + 1); + } + } + printf("\n"); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Tests + +using ::testing::Values; + +// For manual testing only, run all input latency tests and print summary. +IN_PROC_BROWSER_TEST_F(LatencyTest, DISABLED_LatencyWebGLAll) { + RunTest(kWebGL, GetAllBehaviors()); +} + +// For manual testing only, run all input latency tests and print summary. +IN_PROC_BROWSER_TEST_F(LatencyTest, DISABLED_LatencySoftwareAll) { + RunTest(kSoftware, GetAllBehaviors()); +} + +IN_PROC_BROWSER_TEST_P(LatencyTest, LatencySoftware) { + RunTest(kSoftware, std::vector<int>(1, GetParam())); +} + +IN_PROC_BROWSER_TEST_P(LatencyTest, LatencyWebGL) { + RunTest(kWebGL, std::vector<int>(1, GetParam())); +} + +INSTANTIATE_TEST_CASE_P(, LatencyTest, ::testing::Values( + 0, + kInputHeavy, + kInputHeavy | kInputDirty | kRafHeavy, + kInputHeavy | kInputDirty | kRafHeavy | kPaintHeavy, + kInputDirty | kPaintHeavy, + kInputDirty | kRafHeavy | kPaintHeavy + )); + +} // namespace diff --git a/content/browser/renderer_host/render_widget_host.cc b/content/browser/renderer_host/render_widget_host.cc index d48963e8..e0f3137 100644 --- a/content/browser/renderer_host/render_widget_host.cc +++ b/content/browser/renderer_host/render_widget_host.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -1036,6 +1036,12 @@ void RenderWidgetHost::OnMsgUpdateRect( UserMetricsAction("BadMessageTerminate_RWH1")); process()->ReceivedBadMessage(); } else { + UNSHIPPED_TRACE_EVENT_INSTANT2("test_latency", "UpdateRect", + "x+y", params.bitmap_rect.x() + params.bitmap_rect.y(), + "color", 0xffffff & *static_cast<uint32*>(dib->memory())); + UNSHIPPED_TRACE_EVENT_INSTANT1("test_latency", "UpdateRectWidth", + "width", params.bitmap_rect.width()); + // Scroll the backing store. if (!params.scroll_rect.IsEmpty()) { ScrollBackingStoreRect(params.dx, params.dy, diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc index 10c1c5d0..814e454 100644 --- a/gpu/command_buffer/service/gles2_cmd_decoder.cc +++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 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. @@ -3913,6 +3913,8 @@ error::Error GLES2DecoderImpl::HandleRegisterSharedIdsCHROMIUM( void GLES2DecoderImpl::DoClear(GLbitfield mask) { if (CheckBoundFramebuffersValid("glClear")) { + UNSHIPPED_TRACE_EVENT_INSTANT2("test_gpu", "DoClear", "red", clear_red_, + "green", clear_green_); ApplyDirtyState(); glClear(mask); } @@ -4222,6 +4224,7 @@ void GLES2DecoderImpl::DoBlitFramebufferEXT( glBlitFramebufferEXT( srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); } + UNSHIPPED_TRACE_EVENT_INSTANT1("test_gpu", "DoBlit", "width", srcX1 - srcX0); } void GLES2DecoderImpl::DoRenderbufferStorageMultisample( @@ -7338,8 +7341,10 @@ error::Error GLES2DecoderImpl::HandleSwapBuffers( bool is_offscreen = !!offscreen_target_frame_buffer_.get(); int this_frame_number = frame_number_++; // TRACE_EVENT for gpu tests: - TRACE_EVENT_INSTANT1("test_gpu", "SwapBuffers", - "GLImpl", static_cast<int>(gfx::GetGLImplementation())); + TRACE_EVENT_INSTANT2("test_gpu", "SwapBuffers", + "GLImpl", static_cast<int>(gfx::GetGLImplementation()), + "width", (is_offscreen ? offscreen_size_.width() : + surface_->GetSize().width())); TRACE_EVENT2("gpu", "GLES2DecoderImpl::HandleSwapBuffers", "offscreen", is_offscreen, "frame", this_frame_number); |