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

Side by Side Diff: chrome/test/perf/latency/latency_tests.cc

Issue 8883005: Input latency performance test that uses tracing. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: comments Created 9 years 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 | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/command_line.h"
6 #include "base/file_util.h"
7 #include "base/memory/scoped_ptr.h"
8 #include "base/path_service.h"
9 #include "base/string_number_conversions.h"
10 #include "base/stringprintf.h"
11 #include "base/test/test_switches.h"
12 #include "base/test/trace_event_analyzer.h"
13 #include "base/threading/platform_thread.h"
14 #include "base/timer.h"
15 #include "base/version.h"
16 #include "chrome/browser/ui/browser.h"
17 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
18 #include "chrome/common/chrome_paths.h"
19 #include "chrome/common/chrome_switches.h"
20 #include "chrome/test/base/tracing.h"
21 #include "chrome/test/base/ui_test_utils.h"
22 #include "chrome/test/perf/browser_perf_test.h"
23 #include "chrome/test/perf/perf_test.h"
24 #include "content/browser/renderer_host/render_view_host.h"
25 #include "content/browser/tab_contents/tab_contents.h"
26 #include "content/public/common/content_switches.h"
27 #include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h"
28 #include "testing/gtest/include/gtest/gtest.h"
29
30 // Run with --vmodule=latency_tests=1 to print verbose latency info.
31
32 // How is latency measured?
33 //
34 // The test injects mouse moves many times per frame from the browser via
35 // RenderWidgetHost. Each input has a unique x coordinate. When the javascript
36 // handler receives the input, it stores the coordinate for later use in the
37 // requestAnimationFrame callback. In RAF, the test paints using the x
38 // coordinate as a color (in software, it sets the color of a table; in webgl,
39 // it executes a glClearColor). Trace events emit the color when it is picked up
40 // by either UpdateRect for software or gles2_cmd_decoder/glClear for webgl.
41 //
42 // Each UpdateRect (software) or SwapBuffers (webgl) is considered to be a frame
43 // boundary that will be used to measure latency in number of frames. Starting
44 // from a frame boundary Y, the test first determines what mouse x coordinate
45 // was represented by the color at that frame boundary. Then, the test walks
46 // backward through the trace events to find the input event matching that
47 // x coordinate. Then, the test find the nearest frame boundary X to the input
48 // event (may be before or after). The number of frame boundaries is then
49 // counted between X and Y to determine the input latency.
50 //
51 // By injecting mouse moves many times per frame, we reduce flakiness in the
52 // finding of the nearest frame boundary.
53 //
54 // This test only measures the latency introduced by chrome code -- it does not
55 // measure latency introduced by mouse drivers or the GL driver or the OS window
56 // manager. The actual latency seen by a user is more than what is reported by
57 // this test.
58 //
59 // Current modes:
60 // - Software RAF
61 // - WebGL RAF
62
63 namespace {
64
65 using namespace trace_analyzer;
66
67 enum LatencyTestFlags {
68 kInputHeavy = 1 << 0,
69 kInputDirty = 1 << 1,
70 kRafHeavy = 1 << 2,
71 kPaintHeavy = 1 << 3
72 };
73
74 int kWebGLCanvasWidth = 10;
75 int kNumFrames = 60;
76 int kInputsPerFrame = 16;
77 // Magic number to identify certain glClear events.
78 int kClearColorGreen = 137;
79 int kMouseY = 5;
80
81 class LatencyTest : public BrowserPerfTest {
82 public:
83 LatencyTest() :
84 query_instant_(Query(EVENT_PHASE) ==
85 Query::Phase(TRACE_EVENT_PHASE_INSTANT)),
86 query_swaps_(Query::Bool(false)),
87 query_inputs_(Query::Bool(false)),
88 query_blits_(Query::Bool(false)),
89 query_clears_(Query::Bool(false)),
90 mouse_x_(0),
91 tab_width_(0),
92 delay_time_ms_(0),
93 num_frames_(0),
94 verbose_(false),
95 test_flags_(0) {}
96
97 virtual void SetUpCommandLine(CommandLine* command_line) {
98 BrowserPerfTest::SetUpCommandLine(command_line);
99 // This enables DOM automation for tab contents.
100 EnableDOMAutomation();
101 #ifdef TEST_THREADED_COMPOSITING_LATENCY
102 command_line->AppendSwitch(switches::kEnableThreadedCompositing);
103 #endif
104 // Default behavior is to thumbnail the tab after 0.5 seconds, causing
105 // a nasty frame hitch and disturbing the latency test. Fix that:
106 command_line->AppendSwitch(switches::kEnableInBrowserThumbnailing);
107 command_line->AppendSwitch(switches::kDisableBackgroundNetworking);
108 }
109
110 // Run test with specified |mode| and |behaviors|.
111 // |mode| can be webgl or software.
112 // |behaviors| is a list of combinations of LatencyTestFlags.
113 void RunTest(const std::string& mode, const std::vector<int>& behaviors);
114
115 std::vector<int> GetAllBehaviors() {
116 std::vector<int> behaviors;
117 int max_behaviors = kInputHeavy | kInputDirty | kRafHeavy | kPaintHeavy;
118 for (int i = 0; i <= max_behaviors; ++i)
119 behaviors.push_back(i);
120 return behaviors;
121 }
122
123 private:
124 void RunTestInternal(const std::string& test_url,
125 bool send_inputs,
126 int input_delay_ms);
127
128 double CalculateLatency();
129
130 std::string GetTraceName(int flags) {
131 if (flags == 0)
132 return "simple";
133 std::string name;
134 if (flags & kInputHeavy)
135 name += "ih";
136 if (flags & kInputDirty)
137 name += std::string(name.empty()? "" : "_") + "id";
138 if (flags & kRafHeavy)
139 name += std::string(name.empty()? "" : "_") + "rh";
140 if (flags & kPaintHeavy)
141 name += std::string(name.empty()? "" : "_") + "ph";
142 return name;
143 }
144
145 std::string GetUrlModeString(int flags) {
146 std::string mode = "&mode=" + mode_;
147 if (flags & kInputHeavy)
148 mode += "&inputHeavy=true";
149 if (flags & kInputDirty)
150 mode += "&inputDirty=true";
151 if (flags & kRafHeavy)
152 mode += "&rafHeavy=true";
153 if (flags & kPaintHeavy)
154 mode += "&paintHeavy=true";
155 return mode;
156 }
157
158 std::string GetUrl(int flags) {
159 std::string test_url =
160 net::FilePathToFileURL(test_path_).possibly_invalid_spec();
161 test_url += "?numFrames=" + base::IntToString(num_frames_);
162 test_url += "&canvasWidth=" + base::IntToString(kWebGLCanvasWidth);
163 test_url += "&clearColorGreen=" + base::IntToString(kClearColorGreen);
164 test_url += "&delayTimeMS=" + base::IntToString(delay_time_ms_);
165 test_url += "&y=" + base::IntToString(kMouseY);
166 return test_url + GetUrlModeString(flags);
167 }
168
169 int GetMeanFrameTime() const {
170 TraceEventVector events;
171 // Search for compositor swaps (or UpdateRects in the software path).
172 analyzer_->FindEvents(query_swaps_, &events);
173 RateStats stats;
174 bool success = GetRateStats(events, &stats);
175 EXPECT_TRUE(success);
176 if (!success)
177 return 16000;
178
179 // Check that the number of swaps is close to kNumFrames.
180 EXPECT_LT(num_frames_ - num_frames_/4, static_cast<int>(events.size()));
181 return static_cast<int>(stats.mean_us);
182 }
183
184 void SendInput() {
185 RenderViewHost* rvh = browser()->GetSelectedTabContentsWrapper()->
186 tab_contents()->render_view_host();
187 WebKit::WebMouseEvent mouse_event;
188 mouse_event.movementX = 1;
189 mouse_x_ += mouse_event.movementX;
190 // Wrap mouse_x_ when it's near the edge of the tab.
191 if (mouse_x_ > tab_width_ - 5)
192 mouse_x_ = 1;
193 mouse_event.x = mouse_event.windowX = mouse_x_;
194 // Set y coordinate to be a few pixels down from the top of the window,
195 // so that it is between the top and bottom of the canvas.
196 mouse_event.y = mouse_event.windowY = 5;
197 mouse_event.type = WebKit::WebInputEvent::MouseMove;
198 TRACE_EVENT_INSTANT1("test_latency", "MouseEventBegin", "x", mouse_x_);
199 rvh->ForwardMouseEvent(mouse_event);
200 }
201
202 void PrintEvents(const TraceEventVector& events) {
203 bool is_software = (mode_ == "software");
204 // Print trace data.
205 int swap_count = 0;
206 for (size_t i = 0; i < events.size(); ++i) {
207 if (events[i]->name == "MouseEventBegin") {
208 printf("%03d ", events[i]->GetKnownArgAsInt("x"));
209 }
210 else if (events[i]->name == "DoClear") {
211 printf("Clr%03d ", events[i]->GetKnownArgAsInt("red"));
212 }
213 else if (events[i]->name == "DoBlit") {
214 // WebGL context swap buffers.
215 printf("BLT ");
216 }
217 else if (events[i]->name == "SwapBuffers") {
218 // Compositor context swap buffers.
219 ++swap_count;
220 printf("|\nframe %03d: ", swap_count + 1);
221 }
222 else if (is_software && events[i]->name == "UpdateRect") {
223 ++swap_count;
224 printf("(%d)|\nframe %03d: ",
225 events[i]->GetKnownArgAsInt("color"), swap_count + 1);
226 }
227 }
228 printf("\n");
229 }
230
231 FilePath test_path_;
232 Query query_instant_;
233 Query query_swaps_;
234 Query query_inputs_;
235 Query query_blits_;
236 Query query_clears_;
237 scoped_ptr<TraceAnalyzer> analyzer_;
238 int mouse_x_;
239 int tab_width_;
240 base::RepeatingTimer<LatencyTest> timer_;
241 std::string mode_;
242 int delay_time_ms_;
243 int num_frames_;
244 std::map<int, double> latencies_;
245 bool verbose_;
246 int test_flags_;
247 };
248
249 void LatencyTest::RunTest(const std::string& mode,
250 const std::vector<int>& behaviors) {
251 mode_ = mode;
252 verbose_ = (logging::GetVlogLevel("latency_tests") > 0);
253
254 // Construct queries for searching trace events via TraceAnalyzer.
255 int initial_flags = 0;
256 if (mode_ == "webgl") {
257 query_swaps_ = query_instant_ &&
258 Query(EVENT_NAME) == Query::String("SwapBuffers") &&
259 Query(EVENT_ARG, "width") != Query::Int(kWebGLCanvasWidth);
260 } else if (mode_ == "software") {
261 // Run software with kPaintHeavy the first time when no inputs are
262 // triggering paints -- otherwise it won't update each frame.
263 initial_flags = static_cast<int>(kPaintHeavy);
264 // Software updates need to have x=0 and y=0 to contain the input color.
265 query_swaps_ = query_instant_ &&
266 Query(EVENT_NAME) == Query::String("UpdateRect") &&
267 Query(EVENT_ARG, "x+y") == Query::Int(0);
268 }
269 query_inputs_ = query_instant_ &&
270 Query(EVENT_NAME) == Query::String("MouseEventBegin");
271 query_blits_ = query_instant_ &&
272 Query(EVENT_NAME) == Query::String("DoBlit") &&
273 Query(EVENT_ARG, "width") == Query::Int(kWebGLCanvasWidth);
274 query_clears_ = query_instant_ &&
275 Query(EVENT_NAME) == Query::String("DoClear") &&
276 Query(EVENT_ARG, "green") == Query::Int(kClearColorGreen);
277 Query query_width_swaps = query_swaps_;
278 if (mode_ == "software") {
279 query_width_swaps = query_instant_ &&
280 Query(EVENT_NAME) == Query::String("UpdateRectWidth") &&
281 Query(EVENT_ARG, "width") > Query::Int(kWebGLCanvasWidth);
282 }
283
284 // Set path to test html.
285 PathService::Get(chrome::DIR_TEST_DATA, &test_path_);
286 test_path_ = test_path_.Append(FILE_PATH_LITERAL("perf"));
287 test_path_ = test_path_.Append(FILE_PATH_LITERAL("latency"));
288 test_path_ = test_path_.Append(FILE_PATH_LITERAL("latency_suite.html"));
289 ASSERT_TRUE(file_util::PathExists(test_path_))
290 << "Missing test file: " << test_path_.value();
291
292 // Run once with defaults to measure the frame times.
293 delay_time_ms_ = 0;
294 // kNumFrames may be very high, but we only need a few frames to measure
295 // average frame times.
296 num_frames_ = 30;
297 RunTestInternal(GetUrl(initial_flags), false, 0);
298
299 // Get width of tab so that we know the limit of x coordinates for the
300 // injected mouse inputs.
301 const TraceEvent* swap_event = analyzer_->FindOneEvent(query_width_swaps);
302 ASSERT_TRUE(swap_event);
303 tab_width_ = swap_event->GetKnownArgAsInt("width");
304 // Keep printf output clean by limiting input coords to three digits:
305 tab_width_ = (tab_width_ < 1000) ? tab_width_ : 999;
306 // Sanity check the tab_width -- it should be more than 100 pixels.
307 EXPECT_GT(tab_width_, 100);
308
309 int mean_frame_time_us = GetMeanFrameTime();
310 if (verbose_)
311 printf("Mean frame time micros = %d\n", mean_frame_time_us);
312 // Delay time is 2x the average frame time.
313 delay_time_ms_ = 2 * mean_frame_time_us / 1000;
314 // Calculate delay time between inputs based on the measured frame time.
315 // This prevents flooding the browser with more events than we need if the
316 // test is running very slow (such as on a VM).
317 int delay_us = mean_frame_time_us / kInputsPerFrame;
318 // Round delay_us down to nearest milliseconds.
319 int delay_ms = delay_us / 1000;
320 delay_ms = (delay_ms <= 0) ? 1 : delay_ms;
321
322 // Reset num_frames_ for actual test runs.
323 num_frames_ = kNumFrames;
324
325 // Run input latency test with each requested behavior.
326 for (size_t i = 0; i < behaviors.size(); ++i) {
327 // Sleep to allow the rendering pipeline to flush.
328 base::PlatformThread::Sleep(200);
329
330 test_flags_ = behaviors[i];
331 std::string url = GetUrl(test_flags_);
332 printf("=============================================================\n");
333 if (verbose_)
334 printf("Mode: %s\n", GetUrlModeString(i).c_str());
335 printf("URL: %s\n", url.c_str());
336
337 // Do the actual test with input events.
338 RunTestInternal(url, true, delay_ms);
339 latencies_[test_flags_] = CalculateLatency();
340 }
341
342 // Print summary if more than 1 behavior was tested in this run.
343 if (behaviors.size() > 1) {
344 printf("#############################################################\n");
345 printf("## %s\n", mode_.c_str());
346 if (verbose_) {
347 printf("Latency, behavior:\n");
348 for (size_t i = 0; i < behaviors.size(); ++i) {
349 printf("%.1f, %s%s%s%s\n", latencies_[behaviors[i]],
350 (i & kInputHeavy) ? "InputHeavy " : "",
351 (i & kInputDirty) ? "InputDirty " : "",
352 (i & kRafHeavy) ? "RafHeavy " : "",
353 (i & kPaintHeavy) ? "PaintHeavy " : "");
354 }
355 }
356 printf("Latencies for tests: ");
357 for (size_t i = 0; i < behaviors.size(); ++i) {
358 printf("%.1f%s", latencies_[behaviors[i]],
359 (i < behaviors.size() - 1) ? ", " : "");
360 }
361 printf("\n");
362 printf("#############################################################\n");
363 }
364 }
365
366 void LatencyTest::RunTestInternal(const std::string& test_url,
367 bool send_inputs,
368 int input_delay_ms) {
369 mouse_x_ = 0;
370
371 ASSERT_TRUE(tracing::BeginTracing("test_gpu,test_latency"));
372
373 ui_test_utils::NavigateToURLWithDisposition(
374 browser(), GURL(test_url), CURRENT_TAB,
375 ui_test_utils::BROWSER_TEST_NONE);
376
377 // Start sending mouse inputs.
378 if (send_inputs) {
379 timer_.Start(FROM_HERE, base::TimeDelta::FromMicroseconds(input_delay_ms),
380 this, &LatencyTest::SendInput);
381 }
382
383 // Wait for message indicating the test has finished running.
384 ui_test_utils::DOMMessageQueue message_queue;
385 ASSERT_TRUE(message_queue.WaitForMessage(NULL));
386
387 timer_.Stop();
388
389 std::string json_events;
390 ASSERT_TRUE(tracing::EndTracing(&json_events));
391
392 analyzer_.reset(TraceAnalyzer::Create(json_events));
393 analyzer_->AssociateBeginEndEvents();
394 analyzer_->MergeAssociatedEventArgs();
395 }
396
397 double LatencyTest::CalculateLatency() {
398 TraceEventVector events;
399 if (mode_ == "webgl") {
400 // Search for three types of events in WebGL mode:
401 // - onscreen swaps.
402 // - DoClear calls that contain the mouse x coordinate.
403 // - mouse events.
404 analyzer_->FindEvents(query_swaps_ || query_inputs_ ||
405 query_blits_ || query_clears_, &events);
406 }
407 else if (mode_ == "software") {
408 analyzer_->FindEvents(query_swaps_ || query_inputs_, &events);
409 } else {
410 CHECK(0) << "invalid mode";
411 }
412
413 if (verbose_)
414 PrintEvents(events);
415
416 int swap_count = 0;
417 size_t previous_blit_pos = 0;
418 swap_count = 0;
419 std::vector<int> latencies;
420 printf("Measured latency (in number of frames) for each frame:\n");
421 for (size_t i = 0; i < events.size(); ++i) {
422 if (query_swaps_.Evaluate(*events[i])) {
423 // Compositor context swap buffers.
424 ++swap_count;
425 // Don't analyze first few swaps, because they are filling the rendering
426 // pipeline and may be unstable.
427 if (swap_count > 4) {
428 int mouse_x = 0;
429 if (mode_ == "webgl") {
430
cmp 2011/12/20 23:44:51 remove empty line
jbates 2011/12/21 00:39:59 Done.
431 // Trace backwards through the events to find the input event that
432 // matches the glClear that was presented by this SwapBuffers.
433
434 // Step 1: Find the last blit (which will be the WebGL blit).
435 size_t blit_pos = 0;
436 EXPECT_TRUE(FindLastOf(events, query_blits_, i, &blit_pos));
437 // Skip this SwapBuffers if the blit has already been consumed by a
438 // previous SwapBuffers. This means the current frame did not receive
439 // an update from WebGL.
440 EXPECT_GT(blit_pos, previous_blit_pos);
441 if (blit_pos == previous_blit_pos) {
442 if (verbose_)
443 printf(" %03d: ERROR\n", swap_count);
444 else
445 printf(" ERROR");
446 continue;
447 }
448 previous_blit_pos = blit_pos;
449
450 // Step 2: find the last clear from the WebGL blit. This will be the
451 // value of the latest mouse input that has affected this swap.
452 size_t clear_pos = 0;
453 EXPECT_TRUE(FindLastOf(events, query_clears_, blit_pos, &clear_pos));
454 mouse_x = events[clear_pos]->GetKnownArgAsInt("red");
455
cmp 2011/12/20 23:44:51 remove empty line
jbates 2011/12/21 00:39:59 Done.
456 } else if (mode_ == "software") {
457
cmp 2011/12/20 23:44:51 remove empty line
jbates 2011/12/21 00:39:59 Done.
458 // The software path gets the mouse_x directly from the DIB colors.
459 mouse_x = events[i]->GetKnownArgAsInt("color");
460 }
461
462 // Find the corresponding mouse input.
463 size_t input_pos = 0;
464 Query query_mouse_event = query_inputs_ &&
465 Query(EVENT_ARG, "x") == Query::Int(mouse_x);
466 EXPECT_TRUE(FindLastOf(events, query_mouse_event, i, &input_pos));
467
468 // Step 4: Find the nearest onscreen SwapBuffers to this input event.
469 size_t closest_swap = 0;
470 size_t second_closest_swap = 0;
471 EXPECT_TRUE(FindClosest(events, query_swaps_, input_pos,
472 &closest_swap, &second_closest_swap));
473 int latency = CountMatches(events, query_swaps_, closest_swap, i);
474 latencies.push_back(latency);
475 if (verbose_)
476 printf(" %03d: %d\n", swap_count, latency);
477 else
478 printf(" %d", latency);
479 }
480 }
481 }
482 printf("\n");
483
484 EXPECT_GT(latencies.size(), size_t(2));
485 if (latencies.size() <= size_t(2))
486 return 0.0;
487
488 double mean_latency = 0.0;
489 // Skip last couple latencies, because they could be unreliable.
490 size_t num_consider = latencies.size() - 2;
491 for (size_t i = 0; i < num_consider; ++i)
492 mean_latency += static_cast<double>(latencies[i]);
493 mean_latency /= static_cast<double>(num_consider);
494 printf("Mean latency = %f\n", mean_latency);
495
496 double mean_error = 0.0;
497 for (size_t i = 0; i < num_consider; ++i) {
498 double offset = fabs(mean_latency - static_cast<double>(latencies[i]));
499 mean_error = (offset > mean_error) ? offset : mean_error;
500 }
501
502 std::string trace_name = GetTraceName(test_flags_);
503 std::string mean_and_error = base::StringPrintf("%f,%f", mean_latency,
504 mean_error);
505 perf_test::PrintResultMeanAndError(mode_, "", trace_name, mean_and_error,
506 "frames", true);
507 return mean_latency;
508 }
509
510 // For manual testing only, run all input latency tests and print summary.
511 IN_PROC_BROWSER_TEST_F(LatencyTest, DISABLED_WebGLLatencyAll) {
512 RunTest("webgl", GetAllBehaviors());
513 }
514
515 // For manual testing only, run all input latency tests and print summary.
516 IN_PROC_BROWSER_TEST_F(LatencyTest, DISABLED_SoftwareLatencyAll) {
517 RunTest("software", GetAllBehaviors());
518 }
519
520 IN_PROC_BROWSER_TEST_F(LatencyTest, WebGLLatencySelect) {
521 std::vector<int> behaviors;
522 behaviors.push_back(0);
523 behaviors.push_back(kInputHeavy);
524 behaviors.push_back(kInputHeavy | kInputDirty | kRafHeavy);
525 behaviors.push_back(kInputDirty | kPaintHeavy);
526 behaviors.push_back(kInputDirty | kRafHeavy | kPaintHeavy);
527 behaviors.push_back(kInputDirty | kInputHeavy | kRafHeavy | kPaintHeavy);
528 RunTest("webgl", behaviors);
529 }
530
531 IN_PROC_BROWSER_TEST_F(LatencyTest, SoftwareLatencySelect) {
532 std::vector<int> behaviors;
533 behaviors.push_back(0);
534 behaviors.push_back(kInputHeavy);
535 behaviors.push_back(kInputHeavy | kInputDirty | kRafHeavy);
536 behaviors.push_back(kInputDirty | kPaintHeavy);
537 behaviors.push_back(kInputDirty | kRafHeavy | kPaintHeavy);
538 behaviors.push_back(kInputDirty | kInputHeavy | kRafHeavy | kPaintHeavy);
539 RunTest("software", behaviors);
540 }
541
542 } // namespace anonymous
543
cmp 2011/12/20 23:44:51 remove empty line
jbates 2011/12/21 00:39:59 Done.
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698