blob: 1573b2037c0811ad6a5c9759f3b6b10cb2efce8a [file] [log] [blame]
// Copyright 2019 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 <memory>
#include "base/base64.h"
#include "base/bind_helpers.h"
#include "base/run_loop.h"
#include "base/task/thread_pool/thread_pool_instance.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
#include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
#include "chrome/browser/optimization_guide/optimization_guide_session_statistic.h"
#include "chrome/browser/optimization_guide/prediction/prediction_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/services/machine_learning/public/cpp/service_connection.h"
#include "chrome/services/machine_learning/public/mojom/machine_learning_service.mojom.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_switches.h"
#include "components/metrics/content/subprocess_metrics_provider.h"
#include "components/optimization_guide/optimization_guide_constants.h"
#include "components/optimization_guide/optimization_guide_features.h"
#include "components/optimization_guide/optimization_guide_store.h"
#include "components/optimization_guide/optimization_guide_switches.h"
#include "components/optimization_guide/optimization_guide_test_util.h"
#include "components/optimization_guide/proto/models.pb.h"
#include "components/optimization_guide/store_update_data.h"
#include "components/page_load_metrics/browser/page_load_metrics_test_waiter.h"
#include "components/previews/core/previews_switches.h"
#include "components/variations/hashing.h"
#include "content/public/browser/service_process_host.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/network_connection_change_simulator.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
namespace {
// Fetch and calculate the total number of samples from all the bins for
// |histogram_name|. Note: from some browertests run (such as chromeos) there
// might be two profiles created, and this will return the total sample count
// across profiles.
int GetTotalHistogramSamples(const base::HistogramTester* histogram_tester,
const std::string& histogram_name) {
std::vector<base::Bucket> buckets =
histogram_tester->GetAllSamples(histogram_name);
int total = 0;
for (const auto& bucket : buckets)
total += bucket.count;
return total;
}
// Retries fetching |histogram_name| until it contains at least |count| samples.
void RetryForHistogramUntilCountReached(
const base::HistogramTester* histogram_tester,
const std::string& histogram_name,
int count) {
while (true) {
base::ThreadPoolInstance::Get()->FlushForTesting();
base::RunLoop().RunUntilIdle();
content::FetchHistogramsFromChildProcesses();
metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
int total = GetTotalHistogramSamples(histogram_tester, histogram_name);
if (total >= count)
return;
}
}
std::unique_ptr<optimization_guide::proto::PredictionModel>
GetValidDecisionTreePredictionModel() {
std::unique_ptr<optimization_guide::proto::PredictionModel> prediction_model =
GetMinimalDecisionTreePredictionModel(/* threshold= */ 5.0,
/* weight= */ 2.0);
optimization_guide::proto::DecisionTree* decision_tree_model =
prediction_model->mutable_model()->mutable_decision_tree();
optimization_guide::proto::TreeNode* tree_node =
decision_tree_model->mutable_nodes(0);
tree_node->mutable_binary_node()->mutable_left_child_id()->set_value(1);
tree_node->mutable_binary_node()->mutable_right_child_id()->set_value(2);
tree_node->mutable_binary_node()
->mutable_inequality_left_child_test()
->mutable_feature_id()
->mutable_id()
->set_value("agg1");
tree_node->mutable_binary_node()
->mutable_inequality_left_child_test()
->set_type(optimization_guide::proto::InequalityTest::LESS_OR_EQUAL);
tree_node->mutable_binary_node()
->mutable_inequality_left_child_test()
->mutable_threshold()
->set_float_value(1.0);
tree_node = decision_tree_model->add_nodes();
tree_node->mutable_node_id()->set_value(1);
tree_node->mutable_leaf()->mutable_vector()->add_value()->set_double_value(
2.);
tree_node = decision_tree_model->add_nodes();
tree_node->mutable_node_id()->set_value(2);
tree_node->mutable_leaf()->mutable_vector()->add_value()->set_double_value(
4.);
return prediction_model;
}
std::unique_ptr<optimization_guide::proto::PredictionModel>
GetValidEnsemblePredictionModel() {
std::unique_ptr<optimization_guide::proto::PredictionModel> prediction_model =
std::make_unique<optimization_guide::proto::PredictionModel>();
prediction_model->mutable_model()->mutable_threshold()->set_value(5.0);
optimization_guide::proto::Model valid_decision_tree_model =
GetValidDecisionTreePredictionModel()->model();
optimization_guide::proto::Ensemble* ensemble =
prediction_model->mutable_model()->mutable_ensemble();
*ensemble->add_members()->mutable_submodel() = valid_decision_tree_model;
*ensemble->add_members()->mutable_submodel() = valid_decision_tree_model;
return prediction_model;
}
std::unique_ptr<optimization_guide::proto::PredictionModel>
CreatePredictionModel() {
std::unique_ptr<optimization_guide::proto::PredictionModel> prediction_model =
GetValidEnsemblePredictionModel();
optimization_guide::proto::ModelInfo* model_info =
prediction_model->mutable_model_info();
model_info->set_version(1);
model_info->add_supported_model_features(
optimization_guide::proto::
CLIENT_MODEL_FEATURE_EFFECTIVE_CONNECTION_TYPE);
model_info->add_supported_host_model_features("agg1");
model_info->set_optimization_target(
optimization_guide::proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
model_info->add_supported_model_types(
optimization_guide::proto::ModelType::MODEL_TYPE_DECISION_TREE);
return prediction_model;
}
std::unique_ptr<optimization_guide::proto::GetModelsResponse>
BuildGetModelsResponse(
const std::vector<std::string>& hosts,
const std::vector<optimization_guide::proto::ClientModelFeature>&
client_model_features) {
std::unique_ptr<optimization_guide::proto::GetModelsResponse>
get_models_response =
std::make_unique<optimization_guide::proto::GetModelsResponse>();
for (const auto& host : hosts) {
optimization_guide::proto::HostModelFeatures* host_model_features =
get_models_response->add_host_model_features();
host_model_features->set_host(host);
optimization_guide::proto::ModelFeature* model_feature =
host_model_features->add_model_features();
model_feature->set_feature_name("agg1");
model_feature->set_double_value(2.0);
}
std::unique_ptr<optimization_guide::proto::PredictionModel> prediction_model =
CreatePredictionModel();
for (const auto& client_model_feature : client_model_features) {
prediction_model->mutable_model_info()->add_supported_model_features(
client_model_feature);
}
prediction_model->mutable_model_info()->set_version(2);
*get_models_response->add_models() = *prediction_model.get();
return get_models_response;
}
enum class PredictionModelsFetcherRemoteResponseType {
kSuccessfulWithModelsAndFeatures = 0,
kSuccessfulWithFeaturesAndNoModels = 1,
kSuccessfulWithModelsAndNoFeatures = 2,
kUnsuccessful = 3,
};
// A WebContentsObserver that asks whether an optimization target can be
// applied.
class OptimizationGuideConsumerWebContentsObserver
: public content::WebContentsObserver {
public:
explicit OptimizationGuideConsumerWebContentsObserver(
content::WebContents* web_contents)
: content::WebContentsObserver(web_contents) {}
~OptimizationGuideConsumerWebContentsObserver() override = default;
// contents::WebContentsObserver implementation:
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override {
OptimizationGuideKeyedService* service =
OptimizationGuideKeyedServiceFactory::GetForProfile(
Profile::FromBrowserContext(web_contents()->GetBrowserContext()));
if (callback_) {
// Intentionally do not set client model feature values to override to
// make sure decisions are the same in both sync and async variants.
service->ShouldTargetNavigationAsync(
navigation_handle,
optimization_guide::proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD, {},
std::move(callback_));
}
}
void set_callback(
optimization_guide::OptimizationGuideTargetDecisionCallback callback) {
callback_ = std::move(callback);
}
private:
optimization_guide::OptimizationGuideTargetDecisionCallback callback_;
};
// A ServiceProcessHost::Observer that monitors ML Service launch events.
class MLServiceProcessObserver : public content::ServiceProcessHost::Observer {
public:
MLServiceProcessObserver() { content::ServiceProcessHost::AddObserver(this); }
~MLServiceProcessObserver() override {
content::ServiceProcessHost::RemoveObserver(this);
}
MLServiceProcessObserver(const MLServiceProcessObserver&) = delete;
MLServiceProcessObserver& operator=(const MLServiceProcessObserver&) = delete;
// Whether the service is launched.
int IsLaunched() const { return is_launched_; }
// Launch |launch_wait_loop_| to wait until a service launch is detected.
void WaitForLaunch() {
if (!is_launched_)
launch_wait_loop_.Run();
}
void OnServiceProcessLaunched(
const content::ServiceProcessInfo& info) override {
if (info.IsService<machine_learning::mojom::MachineLearningService>()) {
is_launched_ = true;
if (launch_wait_loop_.running())
launch_wait_loop_.Quit();
}
}
private:
base::RunLoop launch_wait_loop_;
bool is_launched_ = false;
};
} // namespace
namespace optimization_guide {
// Abstract base class for browser testing Prediction Manager.
// Actual class fixtures should implement InitializeFeatureList to set up
// features used in tests.
class PredictionManagerBrowserTestBase : public InProcessBrowserTest {
public:
PredictionManagerBrowserTestBase() = default;
~PredictionManagerBrowserTestBase() override = default;
PredictionManagerBrowserTestBase(const PredictionManagerBrowserTestBase&) =
delete;
PredictionManagerBrowserTestBase& operator=(
const PredictionManagerBrowserTestBase&) = delete;
void SetUp() override {
InitializeFeatureList();
models_server_ = std::make_unique<net::EmbeddedTestServer>(
net::EmbeddedTestServer::TYPE_HTTPS);
models_server_->ServeFilesFromSourceDirectory("chrome/test/data/previews");
models_server_->RegisterRequestHandler(base::BindRepeating(
&PredictionManagerBrowserTestBase::HandleGetModelsRequest,
base::Unretained(this)));
ASSERT_TRUE(models_server_->Start());
InProcessBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
content::NetworkConnectionChangeSimulator().SetConnectionType(
network::mojom::ConnectionType::CONNECTION_2G);
https_server_ = std::make_unique<net::EmbeddedTestServer>(
net::EmbeddedTestServer::TYPE_HTTPS);
https_server_->ServeFilesFromSourceDirectory(GetChromeTestDataDir());
ASSERT_TRUE(https_server_->Start());
https_url_with_content_ = https_server_->GetURL("/english_page.html");
https_url_without_content_ = https_server_->GetURL("/empty.html");
// Set up an OptimizationGuideKeyedService consumer.
consumer_ = std::make_unique<OptimizationGuideConsumerWebContentsObserver>(
browser()->tab_strip_model()->GetActiveWebContents());
InProcessBrowserTest::SetUpOnMainThread();
}
void TearDownOnMainThread() override {
EXPECT_TRUE(https_server_->ShutdownAndWaitUntilComplete());
EXPECT_TRUE(models_server_->ShutdownAndWaitUntilComplete());
InProcessBrowserTest::TearDownOnMainThread();
}
void SetUpCommandLine(base::CommandLine* cmd) override {
cmd->AppendSwitch("enable-spdy-proxy-auth");
cmd->AppendSwitch(optimization_guide::switches::
kFetchModelsAndHostModelFeaturesOverrideTimer);
cmd->AppendSwitch(optimization_guide::switches::
kDisableCheckingUserPermissionsForTesting);
cmd->AppendSwitchASCII(optimization_guide::switches::kFetchHintsOverride,
"whatever.com,somehost.com");
cmd->AppendSwitchASCII(
optimization_guide::switches::kOptimizationGuideServiceGetModelsURL,
models_server_
->GetURL(GURL(optimization_guide::
kOptimizationGuideServiceGetModelsDefaultURL)
.host(),
"/")
.spec());
cmd->AppendSwitchASCII("host-rules", "MAP * 127.0.0.1");
cmd->AppendSwitchASCII("force-variation-ids", "4");
}
void SetResponseType(
PredictionModelsFetcherRemoteResponseType response_type) {
response_type_ = response_type;
}
void RegisterWithKeyedService() {
OptimizationGuideKeyedServiceFactory::GetForProfile(browser()->profile())
->RegisterOptimizationTargets(
{optimization_guide::proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD});
}
// Sets the callback on the consumer of the OptimizationGuideKeyedService. If
// set, this will call the async version of ShouldTargetNavigation.
void SetCallbackOnConsumer(
optimization_guide::OptimizationGuideTargetDecisionCallback callback) {
ASSERT_TRUE(consumer_);
consumer_->set_callback(std::move(callback));
}
OptimizationGuideConsumerWebContentsObserver* consumer() {
return consumer_.get();
}
PredictionManager* GetPredictionManager() {
OptimizationGuideKeyedService* optimization_guide_keyed_service =
OptimizationGuideKeyedServiceFactory::GetForProfile(
browser()->profile());
return optimization_guide_keyed_service->GetPredictionManager();
}
std::unique_ptr<page_load_metrics::PageLoadMetricsTestWaiter>
CreatePageLoadMetricsTestWaiter() {
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
return std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>(
web_contents);
}
void SetExpectedFieldTrialNames(
const base::flat_set<uint32_t>& expected_field_trial_name_hashes) {
expected_field_trial_name_hashes_ = expected_field_trial_name_hashes;
}
bool using_ml_service() const { return using_ml_service_; }
GURL https_url_with_content() { return https_url_with_content_; }
GURL https_url_without_content() { return https_url_without_content_; }
protected:
// Virtualize for testing different feature configurations.
virtual void InitializeFeatureList() = 0;
base::test::ScopedFeatureList scoped_feature_list_;
// Feature that the model server should return in response to
// GetModelsRequest.
proto::ClientModelFeature client_model_feature_ =
optimization_guide::proto::CLIENT_MODEL_FEATURE_SITE_ENGAGEMENT_SCORE;
private:
std::unique_ptr<net::test_server::HttpResponse> HandleGetModelsRequest(
const net::test_server::HttpRequest& request) {
std::unique_ptr<net::test_server::BasicHttpResponse> response;
response = std::make_unique<net::test_server::BasicHttpResponse>();
// The request to the remote Optimization Guide Service should always be a
// POST.
EXPECT_EQ(request.method, net::test_server::METHOD_POST);
EXPECT_NE(request.headers.end(), request.headers.find("X-Client-Data"));
optimization_guide::proto::GetModelsRequest models_request;
EXPECT_TRUE(models_request.ParseFromString(request.content));
// Make sure we actually filter field trials appropriately.
EXPECT_EQ(expected_field_trial_name_hashes_.size(),
static_cast<size_t>(models_request.active_field_trials_size()));
base::flat_set<uint32_t> seen_field_trial_name_hashes;
for (const auto& field_trial : models_request.active_field_trials()) {
EXPECT_TRUE(
expected_field_trial_name_hashes_.find(field_trial.name_hash()) !=
expected_field_trial_name_hashes_.end());
seen_field_trial_name_hashes.insert(field_trial.name_hash());
}
EXPECT_EQ(seen_field_trial_name_hashes.size(),
expected_field_trial_name_hashes_.size());
response->set_code(net::HTTP_OK);
std::unique_ptr<optimization_guide::proto::GetModelsResponse>
get_models_response = BuildGetModelsResponse(
{"example1.com", https_server_->GetURL("/").host()},
{client_model_feature_});
if (response_type_ == PredictionModelsFetcherRemoteResponseType::
kSuccessfulWithFeaturesAndNoModels) {
get_models_response->clear_models();
} else if (response_type_ == PredictionModelsFetcherRemoteResponseType::
kSuccessfulWithModelsAndNoFeatures) {
get_models_response->clear_host_model_features();
} else if (response_type_ ==
PredictionModelsFetcherRemoteResponseType::kUnsuccessful) {
response->set_code(net::HTTP_NOT_FOUND);
}
std::string serialized_response;
get_models_response->SerializeToString(&serialized_response);
response->set_content(serialized_response);
return std::move(response);
}
bool using_ml_service_ = false;
GURL https_url_with_content_, https_url_without_content_;
std::unique_ptr<net::EmbeddedTestServer> https_server_;
std::unique_ptr<net::EmbeddedTestServer> models_server_;
PredictionModelsFetcherRemoteResponseType response_type_ =
PredictionModelsFetcherRemoteResponseType::
kSuccessfulWithModelsAndFeatures;
std::unique_ptr<OptimizationGuideConsumerWebContentsObserver> consumer_;
base::flat_set<uint32_t> expected_field_trial_name_hashes_;
};
// Parametrized on whether the ML Service path is enabled.
class PredictionManagerBrowserTest
: public PredictionManagerBrowserTestBase,
public ::testing::WithParamInterface<bool> {
public:
PredictionManagerBrowserTest() : using_ml_service_(GetParam()) {}
~PredictionManagerBrowserTest() override = default;
PredictionManagerBrowserTest(const PredictionManagerBrowserTest&) = delete;
PredictionManagerBrowserTest& operator=(const PredictionManagerBrowserTest&) =
delete;
bool using_ml_service() const { return using_ml_service_; }
private:
void InitializeFeatureList() override {
if (using_ml_service_) {
scoped_feature_list_.InitWithFeatures(
{optimization_guide::features::kOptimizationHints,
optimization_guide::features::kRemoteOptimizationGuideFetching,
optimization_guide::features::kOptimizationTargetPrediction,
optimization_guide::features::
kOptimizationTargetPredictionUsingMLService},
{});
} else {
scoped_feature_list_.InitWithFeatures(
{optimization_guide::features::kOptimizationHints,
optimization_guide::features::kRemoteOptimizationGuideFetching,
optimization_guide::features::kOptimizationTargetPrediction},
{});
}
}
bool using_ml_service_ = false;
};
#if defined(OS_WIN) || defined(OS_MAC) || defined(OS_CHROMEOS)
#define DISABLE_ON_WIN_MAC_CHROMEOS(x) DISABLED_##x
#else
#define DISABLE_ON_WIN_MAC_CHROMEOS(x) x
#endif
INSTANTIATE_TEST_SUITE_P(UsingMLService,
PredictionManagerBrowserTest,
::testing::Bool(),
::testing::PrintToStringParamName());
IN_PROC_BROWSER_TEST_P(
PredictionManagerBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(FCPReachedSessionStatisticsUpdated)) {
RegisterWithKeyedService();
auto waiter = CreatePageLoadMetricsTestWaiter();
waiter->AddPageExpectation(
page_load_metrics::PageLoadMetricsTestWaiter::TimingField::kFirstPaint);
ui_test_utils::NavigateToURL(browser(), https_url_with_content());
waiter->Wait();
const OptimizationGuideSessionStatistic* session_fcp =
GetPredictionManager()->GetFCPSessionStatisticsForTesting();
EXPECT_TRUE(session_fcp);
EXPECT_EQ(1u, session_fcp->GetNumberOfSamples());
}
IN_PROC_BROWSER_TEST_P(
PredictionManagerBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(NoFCPSessionStatisticsUnchanged)) {
RegisterWithKeyedService();
auto waiter = CreatePageLoadMetricsTestWaiter();
waiter->AddPageExpectation(
page_load_metrics::PageLoadMetricsTestWaiter::TimingField::kFirstPaint);
ui_test_utils::NavigateToURL(browser(), https_url_with_content());
waiter->Wait();
const OptimizationGuideSessionStatistic* session_fcp =
GetPredictionManager()->GetFCPSessionStatisticsForTesting();
float current_mean = session_fcp->GetMean();
waiter = CreatePageLoadMetricsTestWaiter();
waiter->AddPageExpectation(
page_load_metrics::PageLoadMetricsTestWaiter::TimingField::kLoadEvent);
ui_test_utils::NavigateToURL(browser(), https_url_without_content());
waiter->Wait();
EXPECT_EQ(1u, session_fcp->GetNumberOfSamples());
EXPECT_EQ(current_mean, session_fcp->GetMean());
}
IN_PROC_BROWSER_TEST_P(
PredictionManagerBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(ModelsAndFeaturesStoreInitialized)) {
base::HistogramTester histogram_tester;
MLServiceProcessObserver ml_service_observer;
content::NetworkConnectionChangeSimulator().SetConnectionType(
network::mojom::ConnectionType::CONNECTION_2G);
RegisterWithKeyedService();
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionManager.PredictionModelsStored", 1);
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionModelLoadedVersion.PainfulPageLoad", 1);
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PredictionManager.HostModelFeaturesStored", true, 1);
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PredictionManager.PredictionModelsStored", true, 1);
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PredictionModelUpdateVersion.PainfulPageLoad", 2, 1);
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PredictionModelLoadedVersion.PainfulPageLoad", 2, 1);
EXPECT_EQ(ml_service_observer.IsLaunched(), using_ml_service());
}
IN_PROC_BROWSER_TEST_P(
PredictionManagerBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(OnlyHostModelFeaturesInGetModelsResponse)) {
base::HistogramTester histogram_tester;
MLServiceProcessObserver ml_service_observer;
SetResponseType(PredictionModelsFetcherRemoteResponseType::
kSuccessfulWithFeaturesAndNoModels);
RegisterWithKeyedService();
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionManager.HostModelFeaturesStored", 1);
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PredictionManager.HostModelFeaturesStored", true, 1);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PredictionManager.PredictionModelsStored", 0);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PredictionModelUpdateVersion.PainfulPageLoad", 0);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PredictionModelLoadedVersion.PainfulPageLoad", 0);
EXPECT_FALSE(ml_service_observer.IsLaunched());
}
IN_PROC_BROWSER_TEST_P(
PredictionManagerBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(OnlyPredictionModelsInGetModelsResponse)) {
base::HistogramTester histogram_tester;
MLServiceProcessObserver ml_service_observer;
SetResponseType(PredictionModelsFetcherRemoteResponseType::
kSuccessfulWithModelsAndNoFeatures);
RegisterWithKeyedService();
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionManager.PredictionModelsStored", 1);
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionModelLoadedVersion.PainfulPageLoad", 1);
// A metadata entry will always be stored for host model features, regardless
// of whether any host model features were actually returned.
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PredictionManager.HostModelFeaturesStored", true, 1);
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PredictionManager.PredictionModelsStored", true, 1);
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PredictionModelUpdateVersion.PainfulPageLoad", 2, 1);
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PredictionModelLoadedVersion.PainfulPageLoad", 2, 1);
EXPECT_EQ(ml_service_observer.IsLaunched(), using_ml_service());
}
IN_PROC_BROWSER_TEST_P(
PredictionManagerBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(PredictionModelFetchFailed)) {
SetResponseType(PredictionModelsFetcherRemoteResponseType::kUnsuccessful);
base::HistogramTester histogram_tester;
MLServiceProcessObserver ml_service_observer;
RegisterWithKeyedService();
// Wait until histograms have been updated before performing checks for
// correct behavior based on the response.
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionModelFetcher.GetModelsResponse.Status", 1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.PredictionModelFetcher.GetModelsResponse.Status",
net::HTTP_NOT_FOUND, 1);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PredictionManager.HostModelFeaturesStored", 0);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PredictionManager.PredictionModelsStored", 0);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PredictionModelUpdateVersion.PainfulPageLoad", 0);
histogram_tester.ExpectTotalCount(
"OptimizationGuide.PredictionModelLoadedVersion.PainfulPageLoad", 0);
EXPECT_FALSE(ml_service_observer.IsLaunched());
}
IN_PROC_BROWSER_TEST_P(
PredictionManagerBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(HostModelFeaturesClearedOnHistoryClear)) {
base::HistogramTester histogram_tester;
MLServiceProcessObserver ml_service_observer;
RegisterWithKeyedService();
// Wait until histograms have been updated before performing checks for
// correct behavior based on the response.
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionModelFetcher.GetModelsResponse.Status", 1);
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionManager.HostModelFeaturesStored", 1);
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionManager.PredictionModelsStored", 1);
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionModelLoadedVersion.PainfulPageLoad", 1);
EXPECT_EQ(ml_service_observer.IsLaunched(), using_ml_service());
SetCallbackOnConsumer(base::DoNothing());
ui_test_utils::NavigateToURL(browser(), https_url_with_content());
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PredictionManager.HasHostModelFeaturesForHost", true,
1);
// Wipe the browser history - clears all the host model features.
browser()->profile()->Wipe();
histogram_tester.ExpectBucketCount(
"OptimizationGuide.ClearHostModelFeatures.StoreAvailable", true, 1);
SetCallbackOnConsumer(base::DoNothing());
ui_test_utils::NavigateToURL(browser(), https_url_with_content());
histogram_tester.ExpectBucketCount(
"OptimizationGuide.PredictionManager.HasHostModelFeaturesForHost", false,
1);
}
class PredictionManagerBrowserSameOriginTest
: public PredictionManagerBrowserTest {
public:
PredictionManagerBrowserSameOriginTest() = default;
~PredictionManagerBrowserSameOriginTest() override = default;
void SetUp() override {
client_model_feature_ =
optimization_guide::proto::CLIENT_MODEL_FEATURE_SAME_ORIGIN_NAVIGATION;
PredictionManagerBrowserTest::SetUp();
}
};
INSTANTIATE_TEST_SUITE_P(UsingMLService,
PredictionManagerBrowserSameOriginTest,
::testing::Bool(),
::testing::PrintToStringParamName());
// Regression test for https://crbug.com/1037945. Tests that the origin of the
// previous navigation is computed correctly.
IN_PROC_BROWSER_TEST_P(PredictionManagerBrowserSameOriginTest,
DISABLE_ON_WIN_MAC_CHROMEOS(IsSameOriginNavigation)) {
base::HistogramTester histogram_tester;
MLServiceProcessObserver ml_service_observer;
RegisterWithKeyedService();
// Wait until histograms have been updated before performing checks for
// correct behavior based on the response.
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionModelFetcher.GetModelsResponse.Status", 1);
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionManager.HostModelFeaturesStored", 1);
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionManager.PredictionModelsStored", 1);
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionModelLoadedVersion.PainfulPageLoad", 1);
EXPECT_EQ(ml_service_observer.IsLaunched(), using_ml_service());
SetCallbackOnConsumer(base::DoNothing());
ui_test_utils::NavigateToURL(browser(), https_url_with_content());
RetryForHistogramUntilCountReached(
&histogram_tester, "OptimizationGuide.PredictionManager.IsSameOrigin", 1);
histogram_tester.ExpectUniqueSample(
"OptimizationGuide.PredictionManager.IsSameOrigin", false, 1);
// Navigate to the same URL in the same tab. This should count as a
// same-origin navigation.
SetCallbackOnConsumer(base::DoNothing());
ui_test_utils::NavigateToURL(browser(), https_url_with_content());
RetryForHistogramUntilCountReached(
&histogram_tester, "OptimizationGuide.PredictionManager.IsSameOrigin", 2);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.PredictionManager.IsSameOrigin", false, 1);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.PredictionManager.IsSameOrigin", true, 1);
// Navigate to a cross-origin URL. This should count as a cross-origin
// navigation.
SetCallbackOnConsumer(base::DoNothing());
ui_test_utils::NavigateToURL(browser(), GURL("https://www.google.com/"));
RetryForHistogramUntilCountReached(
&histogram_tester, "OptimizationGuide.PredictionManager.IsSameOrigin", 3);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.PredictionManager.IsSameOrigin", false, 2);
histogram_tester.ExpectBucketCount(
"OptimizationGuide.PredictionManager.IsSameOrigin", true, 1);
}
IN_PROC_BROWSER_TEST_P(
PredictionManagerBrowserSameOriginTest,
DISABLE_ON_WIN_MAC_CHROMEOS(ShouldTargetNavigationAsync)) {
base::HistogramTester histogram_tester;
MLServiceProcessObserver ml_service_observer;
RegisterWithKeyedService();
// Wait until histograms have been updated before performing checks for
// correct behavior based on the response.
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionModelFetcher.GetModelsResponse.Status", 1);
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionManager.HostModelFeaturesStored", 1);
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionManager.PredictionModelsStored", 1);
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionModelLoadedVersion.PainfulPageLoad", 1);
EXPECT_EQ(ml_service_observer.IsLaunched(), using_ml_service());
std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
SetCallbackOnConsumer(base::BindOnce(
[](base::RunLoop* run_loop,
OptimizationGuideConsumerWebContentsObserver* consumer,
optimization_guide::OptimizationGuideDecision decision) {
// The model should be evaluated with an actual decision since the model
// and all features provided are valid.
EXPECT_NE(decision,
optimization_guide::OptimizationGuideDecision::kUnknown);
run_loop->Quit();
},
run_loop.get(), consumer()));
ui_test_utils::NavigateToURL(browser(), https_url_with_content());
run_loop->Run();
}
class PredictionManagerUsingMLServiceBrowserSameOriginTest
: public PredictionManagerBrowserSameOriginTest {};
// Only instantiate with ML Service enabled.
INSTANTIATE_TEST_SUITE_P(UsingMLService,
PredictionManagerUsingMLServiceBrowserSameOriginTest,
::testing::Values(true),
::testing::PrintToStringParamName());
IN_PROC_BROWSER_TEST_P(
PredictionManagerUsingMLServiceBrowserSameOriginTest,
DISABLE_ON_WIN_MAC_CHROMEOS(
ShouldTargetNavigationAsyncWithServiceDisconnection)) {
base::HistogramTester histogram_tester;
MLServiceProcessObserver ml_service_observer;
RegisterWithKeyedService();
// Wait until histograms have been updated before performing checks for
// correct behavior based on the response.
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionModelFetcher.GetModelsResponse.Status", 1);
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionManager.HostModelFeaturesStored", 1);
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionManager.PredictionModelsStored", 1);
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionModelLoadedVersion.PainfulPageLoad", 1);
EXPECT_TRUE(ml_service_observer.IsLaunched());
// Force termination of the service: model predictors will become invalid.
machine_learning::ServiceConnection::GetInstance()->ResetServiceForTesting();
SetCallbackOnConsumer(base::BindOnce(
[](OptimizationGuideConsumerWebContentsObserver* consumer,
optimization_guide::OptimizationGuideDecision decision) {
EXPECT_EQ(decision,
optimization_guide::OptimizationGuideDecision::kUnknown);
},
consumer()));
ui_test_utils::NavigateToURL(browser(), https_url_with_content());
}
class PredictionManagerUsingMLServiceMetricsOnlyBrowserTest
: public PredictionManagerBrowserTestBase {
public:
PredictionManagerUsingMLServiceMetricsOnlyBrowserTest() = default;
~PredictionManagerUsingMLServiceMetricsOnlyBrowserTest() override = default;
private:
void InitializeFeatureList() override {
scoped_feature_list_.InitWithFeaturesAndParameters(
{
{optimization_guide::features::kOptimizationHints, {}},
{optimization_guide::features::kRemoteOptimizationGuideFetching,
{}},
{optimization_guide::features::kOptimizationTargetPrediction,
{{"painful_page_load_metrics_only", "true"}}},
{optimization_guide::features::
kOptimizationTargetPredictionUsingMLService,
{}},
{optimization_guide::features::kOptimizationHintsFieldTrials,
{{"allowed_field_trial_names",
"scoped_feature_list_trial_for_OptimizationHints,scoped_feature_"
"list_trial_for_OptimizationHintsFetching"}}},
},
{});
SetExpectedFieldTrialNames(base::flat_set<uint32_t>(
{variations::HashName(
"scoped_feature_list_trial_for_OptimizationHints"),
variations::HashName(
"scoped_feature_list_trial_for_OptimizationHintsFetching")}));
}
};
IN_PROC_BROWSER_TEST_F(
PredictionManagerUsingMLServiceMetricsOnlyBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(ShouldTargetNavigationAsync)) {
base::HistogramTester histogram_tester;
MLServiceProcessObserver ml_service_observer;
EXPECT_TRUE(
features::ShouldOverrideOptimizationTargetDecisionForMetricsPurposes(
proto::OptimizationTarget::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD));
RegisterWithKeyedService();
// Wait until histograms have been updated before performing checks for
// correct behavior based on the response.
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionModelFetcher.GetModelsResponse.Status", 1);
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionManager.HostModelFeaturesStored", 1);
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionManager.PredictionModelsStored", 1);
RetryForHistogramUntilCountReached(
&histogram_tester,
"OptimizationGuide.PredictionModelLoadedVersion.PainfulPageLoad", 1);
EXPECT_TRUE(ml_service_observer.IsLaunched());
SetCallbackOnConsumer(base::BindOnce(
[](OptimizationGuideConsumerWebContentsObserver* consumer,
optimization_guide::OptimizationGuideDecision decision) {
EXPECT_EQ(decision,
optimization_guide::OptimizationGuideDecision::kFalse);
},
consumer()));
ui_test_utils::NavigateToURL(browser(), https_url_with_content());
}
} // namespace optimization_guide