1 // Copyright (c) 2012 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.
5 #include "base/command_line.h"
6 #include "base/file_util.h"
7 #include "base/files/file_path.h"
8 #include "base/json/json_reader.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/path_service.h"
11 #include "base/run_loop.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/test/trace_event_analyzer.h"
15 #include "base/values.h"
16 #include "chrome/browser/ui/browser.h"
17 #include "chrome/browser/ui/browser_window.h"
18 #include "chrome/browser/ui/tabs/tab_strip_model.h"
19 #include "chrome/browser/ui/window_snapshot/window_snapshot.h"
20 #include "chrome/common/chrome_paths.h"
21 #include "chrome/common/chrome_switches.h"
22 #include "chrome/common/net/url_fixer_upper.h"
23 #include "chrome/test/base/test_switches.h"
24 #include "chrome/test/base/tracing.h"
25 #include "chrome/test/base/ui_test_utils.h"
26 #include "chrome/test/perf/browser_perf_test.h"
27 #include "chrome/test/perf/perf_test.h"
28 #include "content/public/browser/web_contents.h"
29 #include "content/public/browser/web_contents_view.h"
30 #include "content/public/common/content_switches.h"
31 #include "content/public/test/browser_test_utils.h"
32 #include "content/public/test/test_utils.h"
33 #include "gpu/config/gpu_test_config.h"
34 #include "net/base/net_util.h"
35 #include "net/dns/mock_host_resolver.h"
36 #include "testing/gtest/include/gtest/gtest.h"
37 #include "testing/perf/perf_test.h"
38 #include "third_party/skia/include/core/SkBitmap.h"
39 #include "third_party/skia/include/core/SkColor.h"
40 #include "ui/gfx/codec/png_codec.h"
41 #include "ui/gl/gl_switches.h"
45 #include "base/win/windows_version.h"
52 kInternal = 1 << 0, // Test uses internal test data.
53 kAllowExternalDNS = 1 << 1, // Test needs external DNS lookup.
54 kIsGpuCanvasTest = 1 << 2, // Test uses GPU accelerated canvas features.
58 enum ThroughputTestFlags {
61 kCompositorThread = 1 << 1
64 const int kSpinUpTimeMs = 4 * 1000;
65 const int kRunTimeMs = 10 * 1000;
66 const int kIgnoreSomeFrames = 3;
68 class ThroughputTest : public BrowserPerfTest {
70 explicit ThroughputTest(int flags) :
71 use_gpu_(flags & kGPU),
72 use_compositor_thread_(flags & kCompositorThread),
73 spinup_time_ms_(kSpinUpTimeMs),
74 run_time_ms_(kRunTimeMs) {}
76 // This indicates running on GPU bots, not necessarily using the GPU.
77 bool IsGpuAvailable() const {
78 return CommandLine::ForCurrentProcess()->HasSwitch("enable-gpu");
81 // Parse flags from JSON to control the test behavior.
82 bool ParseFlagsFromJSON(const base::FilePath& json_dir,
83 const std::string& json,
85 scoped_ptr<base::Value> root;
86 root.reset(base::JSONReader::Read(json));
88 ListValue* root_list = NULL;
89 if (!root.get() || !root->GetAsList(&root_list)) {
90 LOG(ERROR) << "JSON missing root list element";
94 DictionaryValue* item = NULL;
95 if (!root_list->GetDictionary(index, &item)) {
96 LOG(ERROR) << "index " << index << " not found in JSON";
101 if (item->GetStringASCII("url", &str)) {
103 } else if (item->GetStringASCII("file", &str)) {
104 base::FilePath empty;
105 gurl_ = URLFixerUpper::FixupRelativeFile(empty, empty.AppendASCII(str));
107 LOG(ERROR) << "missing url or file";
111 if (!gurl_.is_valid()) {
112 LOG(ERROR) << "invalid url: " << gurl_.possibly_invalid_spec();
116 base::FilePath::StringType cache_dir;
117 if (item->GetString("local_path", &cache_dir))
118 local_cache_path_ = json_dir.Append(cache_dir);
121 if (item->GetInteger("spinup_time", &num))
122 spinup_time_ms_ = num * 1000;
123 if (item->GetInteger("run_time", &num))
124 run_time_ms_ = num * 1000;
126 DictionaryValue* pixel = NULL;
127 if (item->GetDictionary("wait_pixel", &pixel)) {
130 if (pixel->GetInteger("x", &x) &&
131 pixel->GetInteger("y", &y) &&
132 pixel->GetString("op", &str) &&
133 pixel->GetList("color", &color) &&
134 color->GetInteger(0, &r) &&
135 color->GetInteger(1, &g) &&
136 color->GetInteger(2, &b)) {
137 wait_for_pixel_.reset(new WaitPixel(x, y, r, g, b, str));
139 LOG(ERROR) << "invalid wait_pixel args";
146 // Parse extra-chrome-flags for extra command line flags.
147 void ParseFlagsFromCommandLine() {
148 if (!CommandLine::ForCurrentProcess()->HasSwitch(
149 switches::kExtraChromeFlags))
151 CommandLine::StringType flags =
152 CommandLine::ForCurrentProcess()->GetSwitchValueNative(
153 switches::kExtraChromeFlags);
154 if (MatchPattern(flags, FILE_PATH_LITERAL("*.json:*"))) {
155 CommandLine::StringType::size_type colon_pos = flags.find_last_of(':');
156 CommandLine::StringType::size_type num_pos = colon_pos + 1;
158 ASSERT_TRUE(base::StringToInt(
159 flags.substr(num_pos, flags.size() - num_pos), &index));
160 base::FilePath filepath(flags.substr(0, colon_pos));
162 ASSERT_TRUE(base::ReadFileToString(filepath, &json));
163 ASSERT_TRUE(ParseFlagsFromJSON(filepath.DirName(), json, index));
169 void AllowExternalDNS() {
170 net::RuleBasedHostResolverProc* resolver =
171 new net::RuleBasedHostResolverProc(host_resolver());
172 resolver->AllowDirectLookup("*");
173 host_resolver_override_.reset(
174 new net::ScopedDefaultHostResolverProc(resolver));
177 void ResetAllowExternalDNS() {
178 host_resolver_override_.reset();
181 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
182 BrowserPerfTest::SetUpCommandLine(command_line);
183 ParseFlagsFromCommandLine();
184 if (!local_cache_path_.value().empty()) {
185 // If --record-mode is already specified, don't set playback-mode.
186 if (!command_line->HasSwitch(switches::kRecordMode))
187 command_line->AppendSwitch(switches::kPlaybackMode);
188 command_line->AppendSwitchNative(switches::kDiskCacheDir,
189 local_cache_path_.value());
190 LOG(INFO) << local_cache_path_.value();
192 // We are measuring throughput, so we don't want any FPS throttling.
193 command_line->AppendSwitch(switches::kDisableGpuVsync);
194 command_line->AppendSwitch(switches::kAllowFileAccessFromFiles);
195 // Enable or disable GPU acceleration.
197 command_line->AppendSwitch(switches::kDisableAcceleratedCompositing);
198 command_line->AppendSwitch(switches::kDisableExperimentalWebGL);
199 command_line->AppendSwitch(switches::kDisableAccelerated2dCanvas);
201 if (use_compositor_thread_) {
202 ASSERT_TRUE(use_gpu_);
203 command_line->AppendSwitch(switches::kEnableThreadedCompositing);
205 command_line->AppendSwitch(switches::kDisableThreadedCompositing);
210 base::RunLoop run_loop;
211 base::MessageLoop::current()->PostDelayedTask(
213 run_loop.QuitClosure(),
214 base::TimeDelta::FromMilliseconds(ms));
215 content::RunThisRunLoop(&run_loop);
218 // Take snapshot of the current tab, encode it as PNG, and save to a SkBitmap.
219 bool TabSnapShotToImage(SkBitmap* bitmap) {
221 std::vector<unsigned char> png;
223 gfx::Rect root_bounds = browser()->window()->GetBounds();
224 gfx::Rect tab_contents_bounds;
225 browser()->tab_strip_model()->GetActiveWebContents()->GetView()->
226 GetContainerBounds(&tab_contents_bounds);
228 gfx::Rect snapshot_bounds(tab_contents_bounds.x() - root_bounds.x(),
229 tab_contents_bounds.y() - root_bounds.y(),
230 tab_contents_bounds.width(),
231 tab_contents_bounds.height());
233 gfx::NativeWindow native_window = browser()->window()->GetNativeWindow();
234 if (!chrome::GrabWindowSnapshotForUser(native_window, &png,
236 LOG(ERROR) << "browser::GrabWindowSnapShot() failed";
240 if (!gfx::PNGCodec::Decode(reinterpret_cast<unsigned char*>(&*png.begin()),
241 png.size(), bitmap)) {
242 LOG(ERROR) << "Decode PNG to a SkBitmap failed";
248 // Check a pixel color every second until it passes test.
249 void WaitForPixelColor() {
250 if (wait_for_pixel_.get()) {
251 bool success = false;
254 ASSERT_TRUE(TabSnapShotToImage(&bitmap));
255 success = wait_for_pixel_->IsDone(bitmap);
262 // flags is one or more of RunTestFlags OR'd together.
263 void RunTest(const std::string& test_name, int flags) {
264 // Set path to test html.
265 base::FilePath test_path;
266 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_path));
267 test_path = test_path.Append(FILE_PATH_LITERAL("perf"));
268 if (flags & kInternal)
269 test_path = test_path.Append(FILE_PATH_LITERAL("private"));
270 test_path = test_path.Append(FILE_PATH_LITERAL("rendering"));
271 test_path = test_path.Append(FILE_PATH_LITERAL("throughput"));
272 test_path = test_path.AppendASCII(test_name);
273 test_path = test_path.Append(FILE_PATH_LITERAL("index.html"));
274 ASSERT_TRUE(base::PathExists(test_path))
275 << "Missing test file: " << test_path.value();
277 gurl_ = net::FilePathToFileURL(test_path);
278 RunTestWithURL(test_name, flags);
281 // flags is one or more of RunTestFlags OR'd together.
282 void RunCanvasBenchTest(const std::string& test_name, int flags) {
283 // Set path to test html.
284 base::FilePath test_path;
285 ASSERT_TRUE(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("canvas_bench"));
288 test_path = test_path.AppendASCII(test_name + ".html");
289 ASSERT_TRUE(base::PathExists(test_path))
290 << "Missing test file: " << test_path.value();
292 gurl_ = net::FilePathToFileURL(test_path);
293 RunTestWithURL(test_name, flags);
296 // flags is one or more of RunTestFlags OR'd together.
297 void RunTestWithURL(int flags) {
298 RunTestWithURL(gurl_.possibly_invalid_spec(), flags);
301 // flags is one or more of RunTestFlags OR'd together.
302 void RunTestWithURL(const std::string& test_name, int flags) {
303 using trace_analyzer::Query;
304 using trace_analyzer::TraceAnalyzer;
305 using trace_analyzer::TraceEventVector;
308 if (use_gpu_ && (flags & kIsGpuCanvasTest) &&
309 base::win::OSInfo::GetInstance()->version() == base::win::VERSION_XP) {
311 LOG(WARNING) << "Test skipped: GPU canvas tests do not run on XP.";
316 if (use_gpu_ && !IsGpuAvailable()) {
317 LOG(WARNING) << "Test skipped: requires gpu. Pass --enable-gpu on the "
318 "command line if use of GPU is desired.";
322 if (flags & kAllowExternalDNS)
325 std::string json_events;
326 TraceEventVector events_sw, events_gpu;
327 scoped_ptr<TraceAnalyzer> analyzer;
329 LOG(INFO) << gurl_.possibly_invalid_spec();
330 ui_test_utils::NavigateToURLWithDisposition(
331 browser(), gurl_, CURRENT_TAB, ui_test_utils::BROWSER_TEST_NONE);
332 content::WaitForLoadStop(
333 browser()->tab_strip_model()->GetActiveWebContents());
335 // Let the test spin up.
336 LOG(INFO) << "Spinning up test...";
337 ASSERT_TRUE(tracing::BeginTracing("test_gpu"));
338 Wait(spinup_time_ms_);
339 ASSERT_TRUE(tracing::EndTracing(&json_events));
341 // Wait for a pixel color to change (if requested).
344 // Check if GPU is rendering:
345 analyzer.reset(TraceAnalyzer::Create(json_events));
346 bool ran_on_gpu = (analyzer->FindEvents(
347 Query::EventNameIs("SwapBuffers"), &events_gpu) > 0u);
348 LOG(INFO) << "Mode: " << (ran_on_gpu ? "GPU" : "Software");
349 EXPECT_EQ(use_gpu_, ran_on_gpu);
351 // Let the test run for a while.
352 LOG(INFO) << "Running test...";
353 ASSERT_TRUE(tracing::BeginTracing("test_fps"));
355 ASSERT_TRUE(tracing::EndTracing(&json_events));
357 // Search for frame ticks. We look for both SW and GPU frame ticks so that
358 // the test can verify that only one or the other are found.
359 analyzer.reset(TraceAnalyzer::Create(json_events));
360 Query query_sw = Query::EventNameIs("TestFrameTickSW");
361 Query query_gpu = Query::EventNameIs("TestFrameTickGPU");
362 analyzer->FindEvents(query_sw, &events_sw);
363 analyzer->FindEvents(query_gpu, &events_gpu);
364 TraceEventVector* frames = NULL;
366 frames = &events_gpu;
367 EXPECT_EQ(0u, events_sw.size());
370 EXPECT_EQ(0u, events_gpu.size());
372 if (!(flags & kIsFlaky)) {
373 ASSERT_GT(frames->size(), 20u);
375 // Cull a few leading and trailing events as they might be unreliable.
376 TraceEventVector rate_events(frames->begin() + kIgnoreSomeFrames,
377 frames->end() - kIgnoreSomeFrames);
378 trace_analyzer::RateStats stats;
379 ASSERT_TRUE(GetRateStats(rate_events, &stats, NULL));
380 LOG(INFO) << "FPS = " << 1000000.0 / stats.mean_us;
382 // Print perf results.
383 double mean_ms = stats.mean_us / 1000.0;
384 double std_dev_ms = stats.standard_deviation_us / 1000.0;
385 std::string trace_name = use_compositor_thread_? "gpu_thread" :
386 ran_on_gpu ? "gpu" : "software";
387 std::string mean_and_error = base::StringPrintf("%f,%f", mean_ms,
389 perf_test::PrintResultMeanAndError(test_name,
396 if (flags & kAllowExternalDNS)
397 ResetAllowExternalDNS();
399 // Close the tab so that we can quit without timing out during the
400 // wait-for-idle stage in browser_test framework.
401 browser()->tab_strip_model()->GetActiveWebContents()->Close();
405 // WaitPixel checks a color against the color at the given pixel coordinates
413 WaitPixel(int x, int y, int r, int g, int b, const std::string& op) :
414 x_(x), y_(y), r_(r), g_(g), b_(b) {
420 CHECK(false) << "op value \"" << op << "\" is not supported";
422 bool IsDone(const SkBitmap& bitmap) {
423 SkColor color = bitmap.getColor(x_, y_);
424 int r = SkColorGetR(color);
425 int g = SkColorGetG(color);
426 int b = SkColorGetB(color);
427 LOG(INFO) << "color(" << x_ << "," << y_ << "): " <<
428 r << "," << g << "," << b;
431 return r != r_ || g != g_ || b != b_;
433 return r == r_ && g == g_ && b == b_;
448 bool use_compositor_thread_;
451 base::FilePath local_cache_path_;
453 scoped_ptr<net::ScopedDefaultHostResolverProc> host_resolver_override_;
454 scoped_ptr<WaitPixel> wait_for_pixel_;
457 // For running tests on GPU:
458 class ThroughputTestGPU : public ThroughputTest {
460 ThroughputTestGPU() : ThroughputTest(kGPU) {}
463 // For running tests on GPU with the compositor thread:
464 class ThroughputTestThread : public ThroughputTest {
466 ThroughputTestThread() : ThroughputTest(kGPU | kCompositorThread) {}
469 // For running tests on Software:
470 class ThroughputTestSW : public ThroughputTest {
472 ThroughputTestSW() : ThroughputTest(kSW) {}
475 ////////////////////////////////////////////////////////////////////////////////
478 #if defined(OS_WIN) && defined(USE_AURA)
480 #define MAYBE(x) DISABLED_ ## x
485 // Run this test with a URL on the command line:
486 // performance_browser_tests --gtest_also_run_disabled_tests --enable-gpu
487 // --gtest_filter=ThroughputTest*URL --extra-chrome-flags=http://...
488 // or, specify a json file with a list of tests, and the index of the test:
489 // --extra-chrome-flags=path/to/tests.json:0
490 // The json format is an array of tests, for example:
492 // {"url":"http://...",
495 // "local_path":"path/to/disk-cache-dir",
496 // "wait_pixel":{"x":10,"y":10,"op":"!=","color":[24,24,24]}},
497 // {"url":"http://..."}
499 // The only required argument is url. If local_path is set, the test goes into
500 // playback-mode and only loads files from the specified cache. If wait_pixel is
501 // specified, then after spinup_time the test waits for the color at the
502 // specified pixel coordinate to match the given color with the given operator.
503 IN_PROC_BROWSER_TEST_F(ThroughputTestSW, DISABLED_TestURL) {
504 RunTestWithURL(kAllowExternalDNS);
507 IN_PROC_BROWSER_TEST_F(ThroughputTestGPU, DISABLED_TestURL) {
508 RunTestWithURL(kAllowExternalDNS);
511 IN_PROC_BROWSER_TEST_F(ThroughputTestGPU, MAYBE(Particles)) {
512 RunTest("particles", kInternal);
515 IN_PROC_BROWSER_TEST_F(ThroughputTestThread, MAYBE(Particles)) {
516 RunTest("particles", kInternal);
519 IN_PROC_BROWSER_TEST_F(ThroughputTestSW, MAYBE(CanvasDemoSW)) {
520 RunTest("canvas-demo", kInternal);
523 IN_PROC_BROWSER_TEST_F(ThroughputTestGPU, MAYBE(CanvasDemoGPU)) {
524 RunTest("canvas-demo", kInternal | kIsGpuCanvasTest);
527 IN_PROC_BROWSER_TEST_F(ThroughputTestThread, MAYBE(CanvasDemoGPU)) {
528 RunTest("canvas-demo", kInternal | kIsGpuCanvasTest);
531 // CompositingHugeDivSW timed out on Mac Intel Release GPU bot
532 // See crbug.com/114781
533 // Stopped producing results in SW: crbug.com/127621
534 IN_PROC_BROWSER_TEST_F(ThroughputTestSW, DISABLED_CompositingHugeDivSW) {
535 RunTest("compositing_huge_div", kNone);
538 // Failing with insufficient frames: crbug.com/127595
539 IN_PROC_BROWSER_TEST_F(ThroughputTestGPU, DISABLED_CompositingHugeDivGPU) {
540 RunTest("compositing_huge_div", kNone);
543 IN_PROC_BROWSER_TEST_F(ThroughputTestSW, MAYBE(DrawImageShadowSW)) {
544 RunTest("canvas2d_balls_with_shadow", kNone);
547 IN_PROC_BROWSER_TEST_F(ThroughputTestGPU, MAYBE(DrawImageShadowGPU)) {
548 // TODO(junov): Fix test flakiness crbug.com/272383
549 RunTest("canvas2d_balls_with_shadow", kNone | kIsGpuCanvasTest | kIsFlaky);
552 // Intermittent failure, should be fixed by converting to telemetry.
553 // See crbug.com/276500 for more details.
554 IN_PROC_BROWSER_TEST_F(ThroughputTestThread, DISABLED_DrawImageShadowGPU) {
555 // TODO(junov): Fix test flakiness crbug.com/272383
556 RunTest("canvas2d_balls_with_shadow", kNone | kIsGpuCanvasTest | kIsFlaky);
559 IN_PROC_BROWSER_TEST_F(ThroughputTestSW, MAYBE(CanvasToCanvasDrawSW)) {
560 if (IsGpuAvailable() &&
561 gpu::GPUTestBotConfig::CurrentConfigMatches("MAC AMD"))
563 RunTest("canvas2d_balls_draw_from_canvas", kNone);
566 IN_PROC_BROWSER_TEST_F(ThroughputTestGPU, MAYBE(CanvasToCanvasDrawGPU)) {
567 if (IsGpuAvailable() &&
568 gpu::GPUTestBotConfig::CurrentConfigMatches("MAC AMD"))
570 RunTest("canvas2d_balls_draw_from_canvas", kNone | kIsGpuCanvasTest);
573 // Failing on windows GPU bots: crbug.com/255192
574 IN_PROC_BROWSER_TEST_F(ThroughputTestSW, DISABLED_CanvasTextSW) {
575 if (IsGpuAvailable() &&
576 gpu::GPUTestBotConfig::CurrentConfigMatches("MAC AMD"))
578 RunTest("canvas2d_balls_text", kNone);
581 IN_PROC_BROWSER_TEST_F(ThroughputTestGPU, MAYBE(CanvasTextGPU)) {
582 RunTest("canvas2d_balls_text", kNone | kIsGpuCanvasTest);
585 IN_PROC_BROWSER_TEST_F(ThroughputTestSW, MAYBE(CanvasFillPathSW)) {
586 RunTest("canvas2d_balls_fill_path", kNone);
589 IN_PROC_BROWSER_TEST_F(ThroughputTestGPU, MAYBE(CanvasFillPathGPU)) {
590 RunTest("canvas2d_balls_fill_path", kNone | kIsGpuCanvasTest);
593 IN_PROC_BROWSER_TEST_F(ThroughputTestSW, MAYBE(CanvasSingleImageSW)) {
594 RunCanvasBenchTest("single_image", kNone);
597 IN_PROC_BROWSER_TEST_F(ThroughputTestGPU, MAYBE(CanvasSingleImageGPU)) {
598 if (IsGpuAvailable() &&
599 gpu::GPUTestBotConfig::CurrentConfigMatches("MAC AMD"))
601 RunCanvasBenchTest("single_image", kNone | kIsGpuCanvasTest);
604 IN_PROC_BROWSER_TEST_F(ThroughputTestSW, MAYBE(CanvasManyImagesSW)) {
605 RunCanvasBenchTest("many_images", kNone);
608 IN_PROC_BROWSER_TEST_F(ThroughputTestGPU, MAYBE(CanvasManyImagesGPU)) {
609 RunCanvasBenchTest("many_images", kNone | kIsGpuCanvasTest);
612 IN_PROC_BROWSER_TEST_F(ThroughputTestThread, MAYBE(CanvasManyImagesGPU)) {
613 RunCanvasBenchTest("many_images", kNone | kIsGpuCanvasTest);