blob: a21c6857ad904d647f6032d85389f285d465b7ea [file] [log] [blame]
// 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/files/file_path.h"
#include "base/json/json_reader.h"
#include "base/memory/scoped_ptr.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/test/trace_event_analyzer.h"
#include "base/values.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/window_snapshot/window_snapshot.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/net/url_fixer_upper.h"
#include "chrome/test/base/test_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/public/browser/web_contents.h"
#include "content/public/browser/web_contents_view.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "gpu/config/gpu_test_config.h"
#include "net/base/net_util.h"
#include "net/dns/mock_host_resolver.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gl/gl_switches.h"
#include "url/gurl.h"
#if defined(OS_WIN)
#include "base/win/windows_version.h"
#endif
namespace {
enum RunTestFlags {
kNone = 0,
kInternal = 1 << 0, // Test uses internal test data.
kAllowExternalDNS = 1 << 1, // Test needs external DNS lookup.
kIsGpuCanvasTest = 1 << 2, // Test uses GPU accelerated canvas features.
kIsFlaky = 1 << 3
};
enum ThroughputTestFlags {
kSW = 0,
kGPU = 1 << 0,
kCompositorThread = 1 << 1
};
const int kSpinUpTimeMs = 4 * 1000;
const int kRunTimeMs = 10 * 1000;
const int kIgnoreSomeFrames = 3;
class ThroughputTest : public BrowserPerfTest {
public:
explicit ThroughputTest(int flags) :
use_gpu_(flags & kGPU),
use_compositor_thread_(flags & kCompositorThread),
spinup_time_ms_(kSpinUpTimeMs),
run_time_ms_(kRunTimeMs) {}
// This indicates running on GPU bots, not necessarily using the GPU.
bool IsGpuAvailable() const {
return CommandLine::ForCurrentProcess()->HasSwitch("enable-gpu");
}
// Parse flags from JSON to control the test behavior.
bool ParseFlagsFromJSON(const base::FilePath& json_dir,
const std::string& json,
int index) {
scoped_ptr<base::Value> root;
root.reset(base::JSONReader::Read(json));
ListValue* root_list = NULL;
if (!root.get() || !root->GetAsList(&root_list)) {
LOG(ERROR) << "JSON missing root list element";
return false;
}
DictionaryValue* item = NULL;
if (!root_list->GetDictionary(index, &item)) {
LOG(ERROR) << "index " << index << " not found in JSON";
return false;
}
std::string str;
if (item->GetStringASCII("url", &str)) {
gurl_ = GURL(str);
} else if (item->GetStringASCII("file", &str)) {
base::FilePath empty;
gurl_ = URLFixerUpper::FixupRelativeFile(empty, empty.AppendASCII(str));
} else {
LOG(ERROR) << "missing url or file";
return false;
}
if (!gurl_.is_valid()) {
LOG(ERROR) << "invalid url: " << gurl_.possibly_invalid_spec();
return false;
}
base::FilePath::StringType cache_dir;
if (item->GetString("local_path", &cache_dir))
local_cache_path_ = json_dir.Append(cache_dir);
int num;
if (item->GetInteger("spinup_time", &num))
spinup_time_ms_ = num * 1000;
if (item->GetInteger("run_time", &num))
run_time_ms_ = num * 1000;
DictionaryValue* pixel = NULL;
if (item->GetDictionary("wait_pixel", &pixel)) {
int x, y, r, g, b;
ListValue* color;
if (pixel->GetInteger("x", &x) &&
pixel->GetInteger("y", &y) &&
pixel->GetString("op", &str) &&
pixel->GetList("color", &color) &&
color->GetInteger(0, &r) &&
color->GetInteger(1, &g) &&
color->GetInteger(2, &b)) {
wait_for_pixel_.reset(new WaitPixel(x, y, r, g, b, str));
} else {
LOG(ERROR) << "invalid wait_pixel args";
return false;
}
}
return true;
}
// Parse extra-chrome-flags for extra command line flags.
void ParseFlagsFromCommandLine() {
if (!CommandLine::ForCurrentProcess()->HasSwitch(
switches::kExtraChromeFlags))
return;
CommandLine::StringType flags =
CommandLine::ForCurrentProcess()->GetSwitchValueNative(
switches::kExtraChromeFlags);
if (MatchPattern(flags, FILE_PATH_LITERAL("*.json:*"))) {
CommandLine::StringType::size_type colon_pos = flags.find_last_of(':');
CommandLine::StringType::size_type num_pos = colon_pos + 1;
int index;
ASSERT_TRUE(base::StringToInt(
flags.substr(num_pos, flags.size() - num_pos), &index));
base::FilePath filepath(flags.substr(0, colon_pos));
std::string json;
ASSERT_TRUE(file_util::ReadFileToString(filepath, &json));
ASSERT_TRUE(ParseFlagsFromJSON(filepath.DirName(), json, index));
} else {
gurl_ = GURL(flags);
}
}
void AllowExternalDNS() {
net::RuleBasedHostResolverProc* resolver =
new net::RuleBasedHostResolverProc(host_resolver());
resolver->AllowDirectLookup("*");
host_resolver_override_.reset(
new net::ScopedDefaultHostResolverProc(resolver));
}
void ResetAllowExternalDNS() {
host_resolver_override_.reset();
}
virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
BrowserPerfTest::SetUpCommandLine(command_line);
ParseFlagsFromCommandLine();
if (!local_cache_path_.value().empty()) {
// If --record-mode is already specified, don't set playback-mode.
if (!command_line->HasSwitch(switches::kRecordMode))
command_line->AppendSwitch(switches::kPlaybackMode);
command_line->AppendSwitchNative(switches::kDiskCacheDir,
local_cache_path_.value());
LOG(INFO) << local_cache_path_.value();
}
// We are measuring throughput, so we don't want any FPS throttling.
command_line->AppendSwitch(switches::kDisableGpuVsync);
command_line->AppendSwitch(switches::kAllowFileAccessFromFiles);
// Enable or disable GPU acceleration.
if (use_gpu_) {
command_line->AppendSwitch(switches::kForceCompositingMode);
} else {
command_line->AppendSwitch(switches::kDisableAcceleratedCompositing);
command_line->AppendSwitch(switches::kDisableExperimentalWebGL);
command_line->AppendSwitch(switches::kDisableAccelerated2dCanvas);
}
if (use_compositor_thread_) {
ASSERT_TRUE(use_gpu_);
command_line->AppendSwitch(switches::kEnableThreadedCompositing);
} else {
command_line->AppendSwitch(switches::kDisableThreadedCompositing);
}
}
void Wait(int ms) {
base::RunLoop run_loop;
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
run_loop.QuitClosure(),
base::TimeDelta::FromMilliseconds(ms));
content::RunThisRunLoop(&run_loop);
}
// Take snapshot of the current tab, encode it as PNG, and save to a SkBitmap.
bool TabSnapShotToImage(SkBitmap* bitmap) {
CHECK(bitmap);
std::vector<unsigned char> png;
gfx::Rect root_bounds = browser()->window()->GetBounds();
gfx::Rect tab_contents_bounds;
browser()->tab_strip_model()->GetActiveWebContents()->GetView()->
GetContainerBounds(&tab_contents_bounds);
gfx::Rect snapshot_bounds(tab_contents_bounds.x() - root_bounds.x(),
tab_contents_bounds.y() - root_bounds.y(),
tab_contents_bounds.width(),
tab_contents_bounds.height());
gfx::NativeWindow native_window = browser()->window()->GetNativeWindow();
if (!chrome::GrabWindowSnapshotForUser(native_window, &png,
snapshot_bounds)) {
LOG(ERROR) << "browser::GrabWindowSnapShot() failed";
return false;
}
if (!gfx::PNGCodec::Decode(reinterpret_cast<unsigned char*>(&*png.begin()),
png.size(), bitmap)) {
LOG(ERROR) << "Decode PNG to a SkBitmap failed";
return false;
}
return true;
}
// Check a pixel color every second until it passes test.
void WaitForPixelColor() {
if (wait_for_pixel_.get()) {
bool success = false;
do {
SkBitmap bitmap;
ASSERT_TRUE(TabSnapShotToImage(&bitmap));
success = wait_for_pixel_->IsDone(bitmap);
if (!success)
Wait(1000);
} while (!success);
}
}
// flags is one or more of RunTestFlags OR'd together.
void RunTest(const std::string& test_name, int flags) {
// Set path to test html.
base::FilePath test_path;
ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_path));
test_path = test_path.Append(FILE_PATH_LITERAL("perf"));
if (flags & kInternal)
test_path = test_path.Append(FILE_PATH_LITERAL("private"));
test_path = test_path.Append(FILE_PATH_LITERAL("rendering"));
test_path = test_path.Append(FILE_PATH_LITERAL("throughput"));
test_path = test_path.AppendASCII(test_name);
test_path = test_path.Append(FILE_PATH_LITERAL("index.html"));
ASSERT_TRUE(base::PathExists(test_path))
<< "Missing test file: " << test_path.value();
gurl_ = net::FilePathToFileURL(test_path);
RunTestWithURL(test_name, flags);
}
// flags is one or more of RunTestFlags OR'd together.
void RunCanvasBenchTest(const std::string& test_name, int flags) {
// Set path to test html.
base::FilePath test_path;
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("canvas_bench"));
test_path = test_path.AppendASCII(test_name + ".html");
ASSERT_TRUE(base::PathExists(test_path))
<< "Missing test file: " << test_path.value();
gurl_ = net::FilePathToFileURL(test_path);
RunTestWithURL(test_name, flags);
}
// flags is one or more of RunTestFlags OR'd together.
void RunTestWithURL(int flags) {
RunTestWithURL(gurl_.possibly_invalid_spec(), flags);
}
// flags is one or more of RunTestFlags OR'd together.
void RunTestWithURL(const std::string& test_name, int flags) {
using trace_analyzer::Query;
using trace_analyzer::TraceAnalyzer;
using trace_analyzer::TraceEventVector;
#if defined(OS_WIN)
if (use_gpu_ && (flags & kIsGpuCanvasTest) &&
base::win::OSInfo::GetInstance()->version() == base::win::VERSION_XP) {
// crbug.com/128208
LOG(WARNING) << "Test skipped: GPU canvas tests do not run on XP.";
return;
}
#endif
if (use_gpu_ && !IsGpuAvailable()) {
LOG(WARNING) << "Test skipped: requires gpu. Pass --enable-gpu on the "
"command line if use of GPU is desired.";
return;
}
if (flags & kAllowExternalDNS)
AllowExternalDNS();
std::string json_events;
TraceEventVector events_sw, events_gpu;
scoped_ptr<TraceAnalyzer> analyzer;
LOG(INFO) << gurl_.possibly_invalid_spec();
ui_test_utils::NavigateToURLWithDisposition(
browser(), gurl_, CURRENT_TAB, ui_test_utils::BROWSER_TEST_NONE);
content::WaitForLoadStop(
browser()->tab_strip_model()->GetActiveWebContents());
// Let the test spin up.
LOG(INFO) << "Spinning up test...";
ASSERT_TRUE(tracing::BeginTracing("test_gpu"));
Wait(spinup_time_ms_);
ASSERT_TRUE(tracing::EndTracing(&json_events));
// Wait for a pixel color to change (if requested).
WaitForPixelColor();
// Check if GPU is rendering:
analyzer.reset(TraceAnalyzer::Create(json_events));
bool ran_on_gpu = (analyzer->FindEvents(
Query::EventNameIs("SwapBuffers"), &events_gpu) > 0u);
LOG(INFO) << "Mode: " << (ran_on_gpu ? "GPU" : "Software");
EXPECT_EQ(use_gpu_, ran_on_gpu);
// Let the test run for a while.
LOG(INFO) << "Running test...";
ASSERT_TRUE(tracing::BeginTracing("test_fps"));
Wait(run_time_ms_);
ASSERT_TRUE(tracing::EndTracing(&json_events));
// Search for frame ticks. We look for both SW and GPU frame ticks so that
// the test can verify that only one or the other are found.
analyzer.reset(TraceAnalyzer::Create(json_events));
Query query_sw = Query::EventNameIs("TestFrameTickSW");
Query query_gpu = Query::EventNameIs("TestFrameTickGPU");
analyzer->FindEvents(query_sw, &events_sw);
analyzer->FindEvents(query_gpu, &events_gpu);
TraceEventVector* frames = NULL;
if (use_gpu_) {
frames = &events_gpu;
EXPECT_EQ(0u, events_sw.size());
} else {
frames = &events_sw;
EXPECT_EQ(0u, events_gpu.size());
}
if (!(flags & kIsFlaky)) {
ASSERT_GT(frames->size(), 20u);
}
// Cull a few leading and trailing events as they might be unreliable.
TraceEventVector rate_events(frames->begin() + kIgnoreSomeFrames,
frames->end() - kIgnoreSomeFrames);
trace_analyzer::RateStats stats;
ASSERT_TRUE(GetRateStats(rate_events, &stats, NULL));
LOG(INFO) << "FPS = " << 1000000.0 / stats.mean_us;
// Print perf results.
double mean_ms = stats.mean_us / 1000.0;
double std_dev_ms = stats.standard_deviation_us / 1000.0;
std::string trace_name = use_compositor_thread_? "gpu_thread" :
ran_on_gpu ? "gpu" : "software";
std::string mean_and_error = base::StringPrintf("%f,%f", mean_ms,
std_dev_ms);
perf_test::PrintResultMeanAndError(test_name,
std::string(),
trace_name,
mean_and_error,
"frame_time",
true);
if (flags & kAllowExternalDNS)
ResetAllowExternalDNS();
// Close the tab so that we can quit without timing out during the
// wait-for-idle stage in browser_test framework.
browser()->tab_strip_model()->GetActiveWebContents()->Close();
}
private:
// WaitPixel checks a color against the color at the given pixel coordinates
// of an SkBitmap.
class WaitPixel {
enum Operator {
EQUAL,
NOT_EQUAL
};
public:
WaitPixel(int x, int y, int r, int g, int b, const std::string& op) :
x_(x), y_(y), r_(r), g_(g), b_(b) {
if (op == "!=")
op_ = EQUAL;
else if (op == "==")
op_ = NOT_EQUAL;
else
CHECK(false) << "op value \"" << op << "\" is not supported";
}
bool IsDone(const SkBitmap& bitmap) {
SkColor color = bitmap.getColor(x_, y_);
int r = SkColorGetR(color);
int g = SkColorGetG(color);
int b = SkColorGetB(color);
LOG(INFO) << "color(" << x_ << "," << y_ << "): " <<
r << "," << g << "," << b;
switch (op_) {
case EQUAL:
return r != r_ || g != g_ || b != b_;
case NOT_EQUAL:
return r == r_ && g == g_ && b == b_;
default:
return false;
}
}
private:
int x_;
int y_;
int r_;
int g_;
int b_;
Operator op_;
};
bool use_gpu_;
bool use_compositor_thread_;
int spinup_time_ms_;
int run_time_ms_;
base::FilePath local_cache_path_;
GURL gurl_;
scoped_ptr<net::ScopedDefaultHostResolverProc> host_resolver_override_;
scoped_ptr<WaitPixel> wait_for_pixel_;
};
// For running tests on GPU:
class ThroughputTestGPU : public ThroughputTest {
public:
ThroughputTestGPU() : ThroughputTest(kGPU) {}
};
// For running tests on GPU with the compositor thread:
class ThroughputTestThread : public ThroughputTest {
public:
ThroughputTestThread() : ThroughputTest(kGPU | kCompositorThread) {}
};
// For running tests on Software:
class ThroughputTestSW : public ThroughputTest {
public:
ThroughputTestSW() : ThroughputTest(kSW) {}
};
////////////////////////////////////////////////////////////////////////////////
/// Tests
// Run this test with a URL on the command line:
// performance_browser_tests --gtest_also_run_disabled_tests --enable-gpu
// --gtest_filter=ThroughputTest*URL --extra-chrome-flags=http://...
// or, specify a json file with a list of tests, and the index of the test:
// --extra-chrome-flags=path/to/tests.json:0
// The json format is an array of tests, for example:
// [
// {"url":"http://...",
// "spinup_time":5,
// "run_time":10,
// "local_path":"path/to/disk-cache-dir",
// "wait_pixel":{"x":10,"y":10,"op":"!=","color":[24,24,24]}},
// {"url":"http://..."}
// ]
// The only required argument is url. If local_path is set, the test goes into
// playback-mode and only loads files from the specified cache. If wait_pixel is
// specified, then after spinup_time the test waits for the color at the
// specified pixel coordinate to match the given color with the given operator.
IN_PROC_BROWSER_TEST_F(ThroughputTestSW, DISABLED_TestURL) {
RunTestWithURL(kAllowExternalDNS);
}
IN_PROC_BROWSER_TEST_F(ThroughputTestGPU, DISABLED_TestURL) {
RunTestWithURL(kAllowExternalDNS);
}
IN_PROC_BROWSER_TEST_F(ThroughputTestGPU, Particles) {
RunTest("particles", kInternal);
}
IN_PROC_BROWSER_TEST_F(ThroughputTestThread, Particles) {
RunTest("particles", kInternal);
}
IN_PROC_BROWSER_TEST_F(ThroughputTestSW, CanvasDemoSW) {
RunTest("canvas-demo", kInternal);
}
IN_PROC_BROWSER_TEST_F(ThroughputTestGPU, CanvasDemoGPU) {
RunTest("canvas-demo", kInternal | kIsGpuCanvasTest);
}
IN_PROC_BROWSER_TEST_F(ThroughputTestThread, CanvasDemoGPU) {
RunTest("canvas-demo", kInternal | kIsGpuCanvasTest);
}
// CompositingHugeDivSW timed out on Mac Intel Release GPU bot
// See crbug.com/114781
// Stopped producing results in SW: crbug.com/127621
IN_PROC_BROWSER_TEST_F(ThroughputTestSW, DISABLED_CompositingHugeDivSW) {
RunTest("compositing_huge_div", kNone);
}
// Failing with insufficient frames: crbug.com/127595
IN_PROC_BROWSER_TEST_F(ThroughputTestGPU, DISABLED_CompositingHugeDivGPU) {
RunTest("compositing_huge_div", kNone);
}
IN_PROC_BROWSER_TEST_F(ThroughputTestSW, DrawImageShadowSW) {
RunTest("canvas2d_balls_with_shadow", kNone);
}
IN_PROC_BROWSER_TEST_F(ThroughputTestGPU, DrawImageShadowGPU) {
// TODO(junov): Fix test flakiness crbug.com/272383
RunTest("canvas2d_balls_with_shadow", kNone | kIsGpuCanvasTest | kIsFlaky);
}
IN_PROC_BROWSER_TEST_F(ThroughputTestThread, DrawImageShadowGPU) {
// TODO(junov): Fix test flakiness crbug.com/272383
RunTest("canvas2d_balls_with_shadow", kNone | kIsGpuCanvasTest | kIsFlaky);
}
IN_PROC_BROWSER_TEST_F(ThroughputTestSW, CanvasToCanvasDrawSW) {
if (IsGpuAvailable() &&
gpu::GPUTestBotConfig::CurrentConfigMatches("MAC AMD"))
return;
RunTest("canvas2d_balls_draw_from_canvas", kNone);
}
IN_PROC_BROWSER_TEST_F(ThroughputTestGPU, CanvasToCanvasDrawGPU) {
if (IsGpuAvailable() &&
gpu::GPUTestBotConfig::CurrentConfigMatches("MAC AMD"))
return;
RunTest("canvas2d_balls_draw_from_canvas", kNone | kIsGpuCanvasTest);
}
// Failing on windows GPU bots: crbug.com/255192
IN_PROC_BROWSER_TEST_F(ThroughputTestSW, DISABLED_CanvasTextSW) {
if (IsGpuAvailable() &&
gpu::GPUTestBotConfig::CurrentConfigMatches("MAC AMD"))
return;
RunTest("canvas2d_balls_text", kNone);
}
IN_PROC_BROWSER_TEST_F(ThroughputTestGPU, CanvasTextGPU) {
RunTest("canvas2d_balls_text", kNone | kIsGpuCanvasTest);
}
IN_PROC_BROWSER_TEST_F(ThroughputTestSW, CanvasFillPathSW) {
RunTest("canvas2d_balls_fill_path", kNone);
}
IN_PROC_BROWSER_TEST_F(ThroughputTestGPU, CanvasFillPathGPU) {
RunTest("canvas2d_balls_fill_path", kNone | kIsGpuCanvasTest);
}
IN_PROC_BROWSER_TEST_F(ThroughputTestSW, CanvasSingleImageSW) {
RunCanvasBenchTest("single_image", kNone);
}
IN_PROC_BROWSER_TEST_F(ThroughputTestGPU, CanvasSingleImageGPU) {
if (IsGpuAvailable() &&
gpu::GPUTestBotConfig::CurrentConfigMatches("MAC AMD"))
return;
RunCanvasBenchTest("single_image", kNone | kIsGpuCanvasTest);
}
IN_PROC_BROWSER_TEST_F(ThroughputTestSW, CanvasManyImagesSW) {
RunCanvasBenchTest("many_images", kNone);
}
IN_PROC_BROWSER_TEST_F(ThroughputTestGPU, CanvasManyImagesGPU) {
RunCanvasBenchTest("many_images", kNone | kIsGpuCanvasTest);
}
IN_PROC_BROWSER_TEST_F(ThroughputTestThread, CanvasManyImagesGPU) {
RunCanvasBenchTest("many_images", kNone | kIsGpuCanvasTest);
}
} // namespace