blob: 6904978cb8236232e1660abf5cab5e9220d4cc7b [file] [log] [blame]
// Copyright 2017 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 "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/browser/predictors/preconnect_manager.h"
#include "chrome/test/base/testing_profile.h"
#include "components/ukm/test_ukm_recorder.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 {
namespace {
const char kInitialUrl[] = "http://www.google.com/cats";
const char kRedirectedUrl[] = "http://www.google.fr/chats";
const char kRedirectedUrl2[] = "http://www.google.de/katzen";
}
using RedirectStatus = ResourcePrefetchPredictor::RedirectStatus;
class LoadingStatsCollectorTest : public testing::Test {
public:
LoadingStatsCollectorTest();
~LoadingStatsCollectorTest() override;
void SetUp() override;
void TestRedirectStatusHistogram(const std::string& initial_url,
const std::string& prediction_url,
const std::string& navigation_url,
RedirectStatus expected_status);
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<base::HistogramTester> histogram_tester_;
std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_;
const net::NetworkIsolationKey network_isolation_key_ =
net::NetworkIsolationKey::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(), config);
histogram_tester_ = std::make_unique<base::HistogramTester>();
ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
content::RunAllTasksUntilIdle();
}
void LoadingStatsCollectorTest::TestRedirectStatusHistogram(
const std::string& initial_url,
const std::string& prediction_url,
const std::string& navigation_url,
RedirectStatus expected_status) {
// Prediction setting.
// We need at least one resource for prediction.
const std::string& script_url = "https://cdn.google.com/script.js";
PreconnectPrediction prediction = CreatePreconnectPrediction(
GURL(prediction_url).host(), initial_url != prediction_url,
{{url::Origin::Create(GURL(script_url)), 1, network_isolation_key_}});
EXPECT_CALL(*mock_predictor_, PredictPreconnectOrigins(GURL(initial_url), _))
.WillOnce(DoAll(SetArgPointee<1>(prediction), Return(true)));
// Navigation simulation.
std::vector<blink::mojom::ResourceLoadInfoPtr> resources;
resources.push_back(
CreateResourceLoadInfoWithRedirects({initial_url, navigation_url}));
resources.push_back(CreateResourceLoadInfo(
script_url, network::mojom::RequestDestination::kScript));
PageRequestSummary summary =
CreatePageRequestSummary(navigation_url, initial_url, resources);
stats_collector_->RecordPageRequestSummary(summary, absl::nullopt);
// Histogram check.
histogram_tester_->ExpectUniqueSample(
"LoadingPredictor.PreconnectLearningRedirectStatus.Navigation",
static_cast<int>(expected_status), 1);
}
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_isolation_key_},
{url::Origin::Create(GURL(gen(1))), 1, network_isolation_key_},
{url::Origin::Create(GURL(gen(2))), 1, network_isolation_key_},
{url::Origin::Create(GURL(gen(3))), 0, network_isolation_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, absl::nullopt);
histogram_tester_->ExpectUniqueSample(
"LoadingPredictor.PreconnectLearningRecall.Navigation", 66, 1);
histogram_tester_->ExpectUniqueSample(
"LoadingPredictor.PreconnectLearningPrecision.Navigation", 50, 1);
histogram_tester_->ExpectUniqueSample(
"LoadingPredictor.PreconnectLearningCount.Navigation", 4, 1);
histogram_tester_->ExpectTotalCount(
"LoadingPredictor.PreconnectLearningRecall.OptimizationGuide", 0);
histogram_tester_->ExpectTotalCount(
"LoadingPredictor.PreconnectLearningPrecision.OptimizationGuide", 0);
histogram_tester_->ExpectTotalCount(
"LoadingPredictor.PreconnectLearningCount.OptimizationGuide", 0);
auto entries = ukm_recorder_->GetEntriesByName(
ukm::builders::LoadingPredictor::kEntryName);
EXPECT_EQ(1u, entries.size());
auto* entry = entries[0];
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);
};
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.
absl::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_isolation_key_},
{url::Origin::Create(GURL(gen(1))), 1, network_isolation_key_},
{url::Origin::Create(GURL(gen(2))), 1, network_isolation_key_},
{url::Origin::Create(GURL(gen(3))), 0, network_isolation_key_}});
optimization_guide_prediction->predicted_subresources = {
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(
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);
histogram_tester_->ExpectUniqueSample(
"LoadingPredictor.PreconnectLearningRecall.OptimizationGuide", 66, 1);
histogram_tester_->ExpectUniqueSample(
"LoadingPredictor.PreconnectLearningPrecision.OptimizationGuide", 50, 1);
histogram_tester_->ExpectUniqueSample(
"LoadingPredictor.PreconnectLearningCount.OptimizationGuide", 4, 1);
histogram_tester_->ExpectTotalCount(
"LoadingPredictor.PreconnectLearningRecall.Navigation", 0);
histogram_tester_->ExpectTotalCount(
"LoadingPredictor.PreconnectLearningPrecision.Navigation", 0);
histogram_tester_->ExpectTotalCount(
"LoadingPredictor.PreconnectLearningCount.Navigation", 0);
auto entries = ukm_recorder_->GetEntriesByName(
ukm::builders::LoadingPredictor::kEntryName);
EXPECT_EQ(1u, entries.size());
auto* entry = entries[0];
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::
kOptimizationGuidePredictionSubresourcesName,
4);
ukm_recorder_->ExpectEntryMetric(
entry,
ukm::builders::LoadingPredictor::
kOptimizationGuidePredictionCorrectlyPredictedSubresourcesName,
1);
// 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, TestRedirectStatusNoRedirect) {
TestRedirectStatusHistogram(kInitialUrl, kInitialUrl, kInitialUrl,
RedirectStatus::NO_REDIRECT);
}
TEST_F(LoadingStatsCollectorTest, TestRedirectStatusNoRedirectButPredicted) {
TestRedirectStatusHistogram(kInitialUrl, kRedirectedUrl, kInitialUrl,
RedirectStatus::NO_REDIRECT_BUT_PREDICTED);
}
TEST_F(LoadingStatsCollectorTest, TestRedirectStatusRedirectNotPredicted) {
TestRedirectStatusHistogram(kInitialUrl, kInitialUrl, kRedirectedUrl,
RedirectStatus::REDIRECT_NOT_PREDICTED);
}
TEST_F(LoadingStatsCollectorTest, TestRedirectStatusRedirectWrongPredicted) {
TestRedirectStatusHistogram(kInitialUrl, kRedirectedUrl, kRedirectedUrl2,
RedirectStatus::REDIRECT_WRONG_PREDICTED);
}
TEST_F(LoadingStatsCollectorTest,
TestRedirectStatusRedirectCorrectlyPredicted) {
TestRedirectStatusHistogram(kInitialUrl, kRedirectedUrl, kRedirectedUrl,
RedirectStatus::REDIRECT_CORRECTLY_PREDICTED);
}
TEST_F(LoadingStatsCollectorTest, TestPreconnectHistograms) {
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);
};
EXPECT_CALL(*mock_predictor_,
PredictPreconnectOrigins(GURL(main_frame_url), _))
.WillOnce(Return(false));
{
// Initialize PreconnectStats.
// These two are hits.
PreconnectedRequestStats origin1(url::Origin::Create(GURL(gen(1))), true);
PreconnectedRequestStats origin2(url::Origin::Create(GURL(gen(2))), false);
// And these two are misses.
PreconnectedRequestStats origin3(url::Origin::Create(GURL(gen(3))), false);
PreconnectedRequestStats origin4(url::Origin::Create(GURL(gen(4))), true);
auto stats = std::make_unique<PreconnectStats>(GURL(main_frame_url));
stats->requests_stats = {origin1, origin2, origin3, origin4};
stats_collector_->RecordPreconnectStats(std::move(stats));
}
{
// Simulate a page load with 3 origins.
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(100), network::mojom::RequestDestination::kScript));
PageRequestSummary summary =
CreatePageRequestSummary(main_frame_url, main_frame_url, resources);
stats_collector_->RecordPageRequestSummary(summary, absl::nullopt);
}
histogram_tester_->ExpectUniqueSample(
internal::kLoadingPredictorPreresolveHitsPercentage, 50, 1);
histogram_tester_->ExpectUniqueSample(
internal::kLoadingPredictorPreconnectHitsPercentage, 50, 1);
histogram_tester_->ExpectUniqueSample(
internal::kLoadingPredictorPreresolveCount, 4, 1);
histogram_tester_->ExpectUniqueSample(
internal::kLoadingPredictorPreconnectCount, 2, 1);
}
// Tests that preconnect histograms won't be recorded if preconnect stats are
// empty.
TEST_F(LoadingStatsCollectorTest, TestPreconnectHistogramsEmpty) {
const std::string main_frame_url = "http://google.com";
auto stats = std::make_unique<PreconnectStats>(GURL(main_frame_url));
stats_collector_->RecordPreconnectStats(std::move(stats));
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, absl::nullopt);
// No histograms should be recorded.
histogram_tester_->ExpectTotalCount(
internal::kLoadingPredictorPreresolveHitsPercentage, 0);
histogram_tester_->ExpectTotalCount(
internal::kLoadingPredictorPreconnectHitsPercentage, 0);
histogram_tester_->ExpectTotalCount(
internal::kLoadingPredictorPreresolveCount, 0);
histogram_tester_->ExpectTotalCount(
internal::kLoadingPredictorPreconnectCount, 0);
// 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());
}
// Tests that the preconnect won't divide by zero if preconnect stats contain
// preresolve attempts only.
TEST_F(LoadingStatsCollectorTest, TestPreconnectHistogramsPreresolvesOnly) {
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);
};
EXPECT_CALL(*mock_predictor_,
PredictPreconnectOrigins(GURL(main_frame_url), _))
.WillOnce(Return(false));
{
// Initialize PreconnectStats.
// These two are hits.
PreconnectedRequestStats origin1(url::Origin::Create(GURL(gen(1))), false);
PreconnectedRequestStats origin2(url::Origin::Create(GURL(gen(2))), false);
// And these two are misses.
PreconnectedRequestStats origin3(url::Origin::Create(GURL(gen(3))), false);
PreconnectedRequestStats origin4(url::Origin::Create(GURL(gen(4))), false);
auto stats = std::make_unique<PreconnectStats>(GURL(main_frame_url));
stats->requests_stats = {origin1, origin2, origin3, origin4};
stats_collector_->RecordPreconnectStats(std::move(stats));
}
{
// Simulate a page load with 3 origins.
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(100), network::mojom::RequestDestination::kScript));
PageRequestSummary summary =
CreatePageRequestSummary(main_frame_url, main_frame_url, resources);
stats_collector_->RecordPageRequestSummary(summary, absl::nullopt);
}
histogram_tester_->ExpectUniqueSample(
internal::kLoadingPredictorPreresolveHitsPercentage, 50, 1);
// Can't really report a hits percentage if there were no events.
histogram_tester_->ExpectTotalCount(
internal::kLoadingPredictorPreconnectHitsPercentage, 0);
histogram_tester_->ExpectUniqueSample(
internal::kLoadingPredictorPreresolveCount, 4, 1);
histogram_tester_->ExpectUniqueSample(
internal::kLoadingPredictorPreconnectCount, 0, 1);
}
} // namespace predictors