blob: 93e6cb144c431e9004c7545dc8983c8a69939501 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/predictors/loading_stats_collector.h"
#include <vector>
#include "base/strings/stringprintf.h"
#include "base/test/metrics/histogram_tester.h"
#include "chrome/browser/predictors/loading_test_util.h"
#include "chrome/test/base/testing_profile.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/browser/preconnect_manager.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_utils.h"
#include "net/base/network_isolation_key.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
using testing::DoAll;
using testing::Return;
using testing::SetArgPointee;
using testing::StrictMock;
namespace predictors {
class LoadingStatsCollectorTest : public testing::Test {
public:
LoadingStatsCollectorTest();
~LoadingStatsCollectorTest() override;
void SetUp() override;
protected:
content::BrowserTaskEnvironment task_environment_;
std::unique_ptr<TestingProfile> profile_;
std::unique_ptr<StrictMock<MockResourcePrefetchPredictor>> mock_predictor_;
std::unique_ptr<LoadingStatsCollector> stats_collector_;
std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_;
const net::NetworkAnonymizationKey network_anonymization_key_ =
net::NetworkAnonymizationKey::CreateTransient();
};
LoadingStatsCollectorTest::LoadingStatsCollectorTest() = default;
LoadingStatsCollectorTest::~LoadingStatsCollectorTest() = default;
void LoadingStatsCollectorTest::SetUp() {
LoadingPredictorConfig config;
PopulateTestConfig(&config);
profile_ = std::make_unique<TestingProfile>();
content::RunAllTasksUntilIdle();
mock_predictor_ = std::make_unique<StrictMock<MockResourcePrefetchPredictor>>(
config, profile_.get());
stats_collector_ =
std::make_unique<LoadingStatsCollector>(mock_predictor_.get());
ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
content::RunAllTasksUntilIdle();
}
TEST_F(LoadingStatsCollectorTest, TestPreconnectPrecisionRecallMetrics) {
const std::string main_frame_url = "http://google.com/?query=cats";
auto gen = [](int index) {
return base::StringPrintf("http://cdn%d.google.com/script.js", index);
};
// Predicts 4 origins: 2 useful, 2 useless.
PreconnectPrediction prediction = CreatePreconnectPrediction(
GURL(main_frame_url).host(), false,
{{url::Origin::Create(GURL(main_frame_url)), 1,
network_anonymization_key_},
{url::Origin::Create(GURL(gen(1))), 1, network_anonymization_key_},
{url::Origin::Create(GURL(gen(2))), 1, network_anonymization_key_},
{url::Origin::Create(GURL(gen(3))), 0, network_anonymization_key_}});
EXPECT_CALL(*mock_predictor_,
PredictPreconnectOrigins(GURL(main_frame_url), _))
.WillOnce(DoAll(SetArgPointee<1>(prediction), Return(true)));
// Simulate a page load with 2 resources, one we know, one we don't, plus we
// know the main frame origin.
std::vector<blink::mojom::ResourceLoadInfoPtr> resources;
resources.push_back(CreateResourceLoadInfo(main_frame_url));
resources.push_back(CreateResourceLoadInfo(
gen(1), network::mojom::RequestDestination::kScript));
resources.push_back(CreateResourceLoadInfo(
gen(100), network::mojom::RequestDestination::kScript));
base::TimeTicks now = base::TimeTicks::Now();
PageRequestSummary summary =
CreatePageRequestSummary(main_frame_url, main_frame_url, resources, now);
summary.navigation_committed = now + base::Milliseconds(3);
summary.preconnect_origins = {
url::Origin::Create(GURL(gen(1))),
url::Origin::Create(GURL(gen(2))),
url::Origin::Create(GURL(gen(3))),
};
stats_collector_->RecordPageRequestSummary(summary, std::nullopt);
auto entries = ukm_recorder_->GetEntriesByName(
ukm::builders::LoadingPredictor::kEntryName);
EXPECT_EQ(1u, entries.size());
auto* entry = entries[0].get();
ukm_recorder_->ExpectEntryMetric(
entry, ukm::builders::LoadingPredictor::kLocalPredictionOriginsName, 4);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::
kLocalPredictionCorrectlyPredictedOriginsName,
2);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::kNavigationStartToNavigationCommitName,
3);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::
kSubresourceOriginPreconnectsInitiatedName,
3);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::
kCorrectSubresourceOriginPreconnectsInitiatedName,
1);
// Make sure optimization guide metrics are not recorded.
EXPECT_FALSE(ukm_recorder_->EntryHasMetric(
entry, ukm::builders::LoadingPredictor::
kOptimizationGuidePredictionDecisionName));
EXPECT_FALSE(ukm_recorder_->EntryHasMetric(
entry, ukm::builders::LoadingPredictor::
kOptimizationGuidePredictionOriginsName));
EXPECT_FALSE(ukm_recorder_->EntryHasMetric(
entry, ukm::builders::LoadingPredictor::
kOptimizationGuidePredictionCorrectlyPredictedOriginsName));
EXPECT_FALSE(ukm_recorder_->EntryHasMetric(
entry, ukm::builders::LoadingPredictor::
kOptimizationGuidePredictionSubresourcesName));
EXPECT_FALSE(ukm_recorder_->EntryHasMetric(
entry,
ukm::builders::LoadingPredictor::
kOptimizationGuidePredictionCorrectlyPredictedSubresourcesName));
// Make sure prefetch metrics are not recorded since no prefetches were
// initiated.
EXPECT_FALSE(ukm_recorder_->EntryHasMetric(
entry, ukm::builders::LoadingPredictor::
kNavigationStartToFirstSubresourcePrefetchInitiatedName));
EXPECT_FALSE(ukm_recorder_->EntryHasMetric(
entry,
ukm::builders::LoadingPredictor::kSubresourcePrefetchesInitiatedName));
EXPECT_FALSE(ukm_recorder_->EntryHasMetric(
entry, ukm::builders::LoadingPredictor::
kCorrectSubresourcePrefetchesInitiatedName));
}
TEST_F(LoadingStatsCollectorTest,
TestPrecisionRecallMetricsWithOptimizationGuide) {
const std::string main_frame_url = "http://google.com/?query=cats";
auto gen = [](int index) {
return base::StringPrintf("http://cdn%d.google.com/script.js", index);
};
const std::string same_origin_subresource_url = "http://google.com/script.js";
PreconnectPrediction local_prediction;
EXPECT_CALL(*mock_predictor_,
PredictPreconnectOrigins(GURL(main_frame_url), _))
.WillOnce(DoAll(SetArgPointee<1>(local_prediction), Return(false)));
// Optimization Guide predicts 4 origins: 2 useful, 2 useless.
std::optional<OptimizationGuidePrediction> optimization_guide_prediction =
OptimizationGuidePrediction();
optimization_guide_prediction->decision =
optimization_guide::OptimizationGuideDecision::kTrue;
base::TimeTicks now = base::TimeTicks::Now();
optimization_guide_prediction->optimization_guide_prediction_arrived =
now + base::Milliseconds(3);
optimization_guide_prediction->preconnect_prediction =
CreatePreconnectPrediction(
GURL(main_frame_url).host(), false,
{{url::Origin::Create(GURL(main_frame_url)), 1,
network_anonymization_key_},
{url::Origin::Create(GURL(gen(1))), 1, network_anonymization_key_},
{url::Origin::Create(GURL(gen(2))), 1, network_anonymization_key_},
{url::Origin::Create(GURL(gen(3))), 0, network_anonymization_key_}});
optimization_guide_prediction->predicted_subresources = {
GURL(same_origin_subresource_url), GURL(gen(1)), GURL(gen(2)),
GURL(gen(3)), GURL(gen(4))};
// Simulate a page load with 2 resources, one we know, one we don't, plus we
// know the main frame origin.
std::vector<blink::mojom::ResourceLoadInfoPtr> resources;
resources.push_back(CreateResourceLoadInfo(main_frame_url));
resources.push_back(
CreateResourceLoadInfo(same_origin_subresource_url,
network::mojom::RequestDestination::kScript));
resources.push_back(CreateResourceLoadInfo(
gen(1), network::mojom::RequestDestination::kScript));
resources.push_back(CreateResourceLoadInfo(
gen(100), network::mojom::RequestDestination::kScript));
PageRequestSummary summary =
CreatePageRequestSummary(main_frame_url, main_frame_url, resources, now);
summary.prefetch_urls = {
GURL(gen(1)),
GURL(gen(2)),
GURL(gen(3)),
};
summary.first_prefetch_initiated = now + base::Milliseconds(1);
stats_collector_->RecordPageRequestSummary(summary,
optimization_guide_prediction);
auto entries = ukm_recorder_->GetEntriesByName(
ukm::builders::LoadingPredictor::kEntryName);
EXPECT_EQ(1u, entries.size());
auto* entry = entries[0].get();
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::kSubresourcePrefetchesInitiatedName, 3);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::
kCorrectSubresourcePrefetchesInitiatedName,
1);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::
kNavigationStartToFirstSubresourcePrefetchInitiatedName,
1);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::kOptimizationGuidePredictionDecisionName,
static_cast<int64_t>(
optimization_guide::OptimizationGuideDecision::kTrue));
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::
kNavigationStartToOptimizationGuidePredictionArrivedName,
3);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::kOptimizationGuidePredictionOriginsName,
4);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::
kOptimizationGuidePredictionCorrectlyPredictedOriginsName,
1);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::
kOptimizationGuidePredictionCorrectlyPredictedLowPriorityOriginsName,
0);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::
kOptimizationGuidePredictionSubresourcesName,
5);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::
kOptimizationGuidePredictionSubresources_CrossOriginName,
4);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::
kOptimizationGuidePredictionCorrectlyPredictedSubresourcesName,
2);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::
kOptimizationGuidePredictionCorrectlyPredictedSubresources_CrossOriginName,
1);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::
kOptimizationGuidePredictionCorrectlyPredictedLowPrioritySubresourcesName,
0);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::
kOptimizationGuidePredictionCorrectlyPredictedLowPrioritySubresources_CrossOriginName,
0);
// Make sure local metrics are not recorded since there was not a local
// prediction.
EXPECT_FALSE(ukm_recorder_->EntryHasMetric(
entry, ukm::builders::LoadingPredictor::kLocalPredictionOriginsName));
EXPECT_FALSE(ukm_recorder_->EntryHasMetric(
entry, ukm::builders::LoadingPredictor::
kLocalPredictionCorrectlyPredictedOriginsName));
// Make sure preconnect metrics are not recorded since no preconnects were
// initiated.
EXPECT_FALSE(ukm_recorder_->EntryHasMetric(
entry, ukm::builders::LoadingPredictor::
kSubresourceOriginPreconnectsInitiatedName));
EXPECT_FALSE(ukm_recorder_->EntryHasMetric(
entry, ukm::builders::LoadingPredictor::
kCorrectSubresourceOriginPreconnectsInitiatedName));
}
TEST_F(LoadingStatsCollectorTest,
TestOptimizationGuideCorrectPredictionsPostLoad) {
const std::string main_frame_url = "http://google.com/?query=cats";
const std::string same_origin_subresource_url = "http://google.com/script.js";
auto gen = [](int index) {
return base::StringPrintf("http://cdn%d.google.com/script.js", index);
};
PreconnectPrediction local_prediction;
EXPECT_CALL(*mock_predictor_,
PredictPreconnectOrigins(GURL(main_frame_url), _))
.WillOnce(DoAll(SetArgPointee<1>(local_prediction), Return(false)));
std::optional<OptimizationGuidePrediction> optimization_guide_prediction =
OptimizationGuidePrediction();
optimization_guide_prediction->decision =
optimization_guide::OptimizationGuideDecision::kTrue;
base::TimeTicks now = base::TimeTicks::Now();
optimization_guide_prediction->optimization_guide_prediction_arrived =
now + base::Milliseconds(3);
optimization_guide_prediction->preconnect_prediction =
CreatePreconnectPrediction(
GURL(main_frame_url).host(), false,
{{url::Origin::Create(GURL(main_frame_url)), 1,
network_anonymization_key_},
{url::Origin::Create(GURL(gen(1))), 1, network_anonymization_key_},
{url::Origin::Create(GURL(gen(2))), 1, network_anonymization_key_},
{url::Origin::Create(GURL(gen(3))), 0, network_anonymization_key_}});
optimization_guide_prediction->predicted_subresources = {
GURL(same_origin_subresource_url), GURL(gen(1)), GURL(gen(2)),
GURL(gen(3)), GURL(gen(4))};
// Simulate a page load with 3 resources (we know all 3). The 3rd resource
// is fetched after the page finishes loading.
std::vector<blink::mojom::ResourceLoadInfoPtr> resources;
resources.push_back(CreateResourceLoadInfo(main_frame_url));
resources.push_back(CreateResourceLoadInfo(
gen(1), network::mojom::RequestDestination::kScript));
resources.push_back(CreateResourceLoadInfo(
gen(2), network::mojom::RequestDestination::kScript));
resources.push_back(CreateResourceLoadInfo(
gen(3), network::mojom::RequestDestination::kScript));
resources.push_back(
CreateResourceLoadInfo(same_origin_subresource_url,
network::mojom::RequestDestination::kScript));
PageRequestSummary summary =
CreatePageRequestSummary(main_frame_url, main_frame_url, {}, now,
/*main_frame_load_complete=*/false);
summary.UpdateOrAddResource(*resources[0]);
summary.UpdateOrAddResource(*resources[1]);
summary.UpdateOrAddResource(*resources[2]);
summary.MainFrameLoadComplete();
summary.UpdateOrAddResource(*resources[3]);
summary.UpdateOrAddResource(*resources[2]);
summary.UpdateOrAddResource(*resources[4]);
summary.prefetch_urls = {GURL(gen(1)), GURL(gen(2)), GURL(gen(3)),
GURL(gen(4))};
summary.first_prefetch_initiated = now + base::Milliseconds(1);
stats_collector_->RecordPageRequestSummary(summary,
optimization_guide_prediction);
auto entries = ukm_recorder_->GetEntriesByName(
ukm::builders::LoadingPredictor::kEntryName);
EXPECT_EQ(1u, entries.size());
auto* entry = entries[0].get();
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::kSubresourcePrefetchesInitiatedName, 4);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::
kCorrectSubresourcePrefetchesInitiatedName,
2);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::
kNavigationStartToFirstSubresourcePrefetchInitiatedName,
1);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::kOptimizationGuidePredictionDecisionName,
static_cast<int64_t>(
optimization_guide::OptimizationGuideDecision::kTrue));
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::
kNavigationStartToOptimizationGuidePredictionArrivedName,
3);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::kOptimizationGuidePredictionOriginsName,
4);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::
kOptimizationGuidePredictionCorrectlyPredictedOriginsName,
2);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::
kOptimizationGuidePredictionCorrectlyPredictedLowPriorityOriginsName,
1);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::
kOptimizationGuidePredictionSubresourcesName,
5);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::
kOptimizationGuidePredictionSubresources_CrossOriginName,
4);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::
kOptimizationGuidePredictionCorrectlyPredictedSubresourcesName,
2);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::
kOptimizationGuidePredictionCorrectlyPredictedSubresources_CrossOriginName,
2);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::
kOptimizationGuidePredictionCorrectlyPredictedLowPrioritySubresourcesName,
2);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::
kOptimizationGuidePredictionCorrectlyPredictedLowPrioritySubresources_CrossOriginName,
1);
}
TEST_F(LoadingStatsCollectorTest, TestNoMetricsRecordedBeforeLoadComplete) {
const std::string main_frame_url = "http://google.com/?query=cats";
auto gen = [](int index) {
return base::StringPrintf("http://cdn%d.google.com/script.js", index);
};
std::optional<OptimizationGuidePrediction> optimization_guide_prediction =
OptimizationGuidePrediction();
optimization_guide_prediction->decision =
optimization_guide::OptimizationGuideDecision::kTrue;
base::TimeTicks now = base::TimeTicks::Now();
optimization_guide_prediction->optimization_guide_prediction_arrived =
now + base::Milliseconds(3);
optimization_guide_prediction->preconnect_prediction =
CreatePreconnectPrediction(GURL(main_frame_url).host(), false, {});
optimization_guide_prediction->predicted_subresources = {GURL(gen(1))};
std::vector<blink::mojom::ResourceLoadInfoPtr> resources;
resources.push_back(CreateResourceLoadInfo(main_frame_url));
resources.push_back(CreateResourceLoadInfo(
gen(1), network::mojom::RequestDestination::kScript));
PageRequestSummary summary =
CreatePageRequestSummary(main_frame_url, main_frame_url, {}, now,
/*main_frame_load_complete=*/false);
summary.UpdateOrAddResource(*resources[0]);
summary.UpdateOrAddResource(*resources[1]);
stats_collector_->RecordPageRequestSummary(summary,
optimization_guide_prediction);
auto entries = ukm_recorder_->GetEntriesByName(
ukm::builders::LoadingPredictor::kEntryName);
EXPECT_EQ(0u, entries.size());
}
// Tests that UKMs are not recorded if we did not have any prediction for a
// navigation.
TEST_F(LoadingStatsCollectorTest, TestNoPrediction) {
const std::string main_frame_url = "http://google.com";
EXPECT_CALL(*mock_predictor_,
PredictPreconnectOrigins(GURL(main_frame_url), _))
.WillOnce(Return(false));
std::vector<blink::mojom::ResourceLoadInfoPtr> resources;
resources.push_back(CreateResourceLoadInfo(main_frame_url));
resources.push_back(
CreateResourceLoadInfo("http://cdn.google.com/script.js",
network::mojom::RequestDestination::kScript));
base::TimeTicks now = base::TimeTicks::Now();
PageRequestSummary summary =
CreatePageRequestSummary(main_frame_url, main_frame_url, resources, now);
summary.navigation_committed = now + base::Milliseconds(3);
stats_collector_->RecordPageRequestSummary(summary, std::nullopt);
// UKM should not be recorded since we did not have predictions for the
// navigation.
auto entries = ukm_recorder_->GetEntriesByName(
ukm::builders::LoadingPredictor::kEntryName);
EXPECT_EQ(0u, entries.size());
}
} // namespace predictors