blob: db522c2f3a2bdce4cfdb6bfd788b6808ad459ad6 [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/page_load_metrics/observers/ukm_page_load_metrics_observer.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/metrics_hashes.h"
#include "base/optional.h"
#include "base/time/time.h"
#include "chrome/browser/page_load_metrics/observers/page_load_metrics_observer_test_harness.h"
#include "chrome/browser/page_load_metrics/page_load_tracker.h"
#include "chrome/common/page_load_metrics/test/page_load_metrics_test_util.h"
#include "components/metrics/proto/system_profile.pb.h"
#include "components/ukm/ukm_source.h"
#include "content/public/test/navigation_simulator.h"
#include "net/nqe/effective_connection_type.h"
#include "net/nqe/network_quality_provider.h"
#include "testing/gmock/include/gmock/gmock.h"
using testing::AnyNumber;
using testing::Mock;
using testing::Return;
namespace {
const char kTestUrl1[] = "https://www.google.com/";
const char kTestUrl2[] = "https://www.example.com/";
class MockNetworkQualityProvider : public net::NetworkQualityProvider {
public:
MOCK_CONST_METHOD0(GetEffectiveConnectionType,
net::EffectiveConnectionType());
MOCK_CONST_METHOD0(GetHttpRTT, base::Optional<base::TimeDelta>());
MOCK_CONST_METHOD0(GetTransportRTT, base::Optional<base::TimeDelta>());
};
} // namespace
class UkmPageLoadMetricsObserverTest
: public page_load_metrics::PageLoadMetricsObserverTestHarness {
protected:
void RegisterObservers(page_load_metrics::PageLoadTracker* tracker) override {
tracker->AddObserver(base::MakeUnique<UkmPageLoadMetricsObserver>(
&mock_network_quality_provider_));
}
void SetUp() override {
page_load_metrics::PageLoadMetricsObserverTestHarness::SetUp();
EXPECT_CALL(mock_network_quality_provider_, GetEffectiveConnectionType())
.Times(AnyNumber())
.WillRepeatedly(Return(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN));
EXPECT_CALL(mock_network_quality_provider_, GetHttpRTT())
.Times(AnyNumber())
.WillRepeatedly(Return(base::Optional<base::TimeDelta>()));
EXPECT_CALL(mock_network_quality_provider_, GetTransportRTT())
.Times(AnyNumber())
.WillRepeatedly(Return(base::Optional<base::TimeDelta>()));
}
MockNetworkQualityProvider& mock_network_quality_provider() {
return mock_network_quality_provider_;
}
private:
MockNetworkQualityProvider mock_network_quality_provider_;
};
TEST_F(UkmPageLoadMetricsObserverTest, NoMetrics) {
EXPECT_EQ(0ul, test_ukm_recorder().sources_count());
EXPECT_EQ(0ul, test_ukm_recorder().entries_count());
}
TEST_F(UkmPageLoadMetricsObserverTest, Basic) {
// PageLoadTiming with all recorded metrics other than FMP. This allows us to
// verify both that all metrics are logged, and that we don't log metrics that
// aren't present in the PageLoadTiming struct. Logging of FMP is verified in
// the FirstMeaningfulPaint test below.
page_load_metrics::mojom::PageLoadTiming timing;
page_load_metrics::InitPageLoadTimingForTest(&timing);
timing.navigation_start = base::Time::FromDoubleT(1);
timing.parse_timing->parse_start = base::TimeDelta::FromMilliseconds(100);
timing.document_timing->dom_content_loaded_event_start =
base::TimeDelta::FromMilliseconds(200);
timing.paint_timing->first_paint = base::TimeDelta::FromMilliseconds(250);
timing.paint_timing->first_contentful_paint =
base::TimeDelta::FromMilliseconds(300);
timing.document_timing->load_event_start =
base::TimeDelta::FromMilliseconds(500);
PopulateRequiredTimingFields(&timing);
NavigateAndCommit(GURL(kTestUrl1));
SimulateTimingUpdate(timing);
// Simulate closing the tab.
DeleteContents();
EXPECT_EQ(1ul, test_ukm_recorder().sources_count());
const ukm::UkmSource* source = test_ukm_recorder().GetSourceForUrl(kTestUrl1);
EXPECT_EQ(GURL(kTestUrl1), source->url());
EXPECT_GE(test_ukm_recorder().entries_count(), 1ul);
test_ukm_recorder().ExpectMetric(*source, internal::kUkmPageLoadEventName,
internal::kUkmPageTransition,
ui::PAGE_TRANSITION_LINK);
test_ukm_recorder().ExpectMetric(*source, internal::kUkmPageLoadEventName,
internal::kUkmParseStartName, 100);
test_ukm_recorder().ExpectMetric(*source, internal::kUkmPageLoadEventName,
internal::kUkmDomContentLoadedName, 200);
test_ukm_recorder().ExpectMetric(*source, internal::kUkmPageLoadEventName,
internal::kUkmFirstPaintName, 250);
test_ukm_recorder().ExpectMetric(*source, internal::kUkmPageLoadEventName,
internal::kUkmFirstContentfulPaintName, 300);
test_ukm_recorder().ExpectMetric(*source, internal::kUkmPageLoadEventName,
internal::kUkmLoadEventName, 500);
EXPECT_FALSE(
test_ukm_recorder().HasMetric(*source, internal::kUkmPageLoadEventName,
internal::kUkmFirstMeaningfulPaintName));
EXPECT_TRUE(
test_ukm_recorder().HasMetric(*source, internal::kUkmPageLoadEventName,
internal::kUkmForegroundDurationName));
}
TEST_F(UkmPageLoadMetricsObserverTest, FailedProvisionalLoad) {
EXPECT_CALL(mock_network_quality_provider(), GetEffectiveConnectionType())
.WillRepeatedly(Return(net::EFFECTIVE_CONNECTION_TYPE_2G));
// The following simulates a navigation that fails and should commit an error
// page, but finishes before the error page actually commits.
GURL url(kTestUrl1);
std::unique_ptr<content::NavigationSimulator> navigation =
content::NavigationSimulator::CreateRendererInitiated(url, main_rfh());
navigation->Fail(net::ERR_TIMED_OUT);
content::RenderFrameHostTester::For(main_rfh())->SimulateNavigationStop();
// Simulate closing the tab.
DeleteContents();
EXPECT_EQ(1ul, test_ukm_recorder().sources_count());
const ukm::UkmSource* source = test_ukm_recorder().GetSourceForUrl(kTestUrl1);
EXPECT_EQ(GURL(kTestUrl1), source->url());
EXPECT_GE(test_ukm_recorder().entries_count(), 1ul);
// Make sure that only the following metrics are logged. In particular, no
// paint/document/etc timing metrics should be logged for failed provisional
// loads.
EXPECT_EQ(5, test_ukm_recorder().CountMetricsForEventName(
*source, internal::kUkmPageLoadEventName));
test_ukm_recorder().ExpectMetric(*source, internal::kUkmPageLoadEventName,
internal::kUkmPageTransition,
ui::PAGE_TRANSITION_LINK);
test_ukm_recorder().ExpectMetric(
*source, internal::kUkmPageLoadEventName,
internal::kUkmEffectiveConnectionType,
metrics::SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_2G);
test_ukm_recorder().ExpectMetric(
*source, internal::kUkmPageLoadEventName, internal::kUkmNetErrorCode,
static_cast<int64_t>(net::ERR_TIMED_OUT) * -1);
EXPECT_TRUE(
test_ukm_recorder().HasMetric(*source, internal::kUkmPageLoadEventName,
internal::kUkmForegroundDurationName));
EXPECT_TRUE(
test_ukm_recorder().HasMetric(*source, internal::kUkmPageLoadEventName,
internal::kUkmFailedProvisionaLoadName));
}
TEST_F(UkmPageLoadMetricsObserverTest, FirstMeaningfulPaint) {
page_load_metrics::mojom::PageLoadTiming timing;
page_load_metrics::InitPageLoadTimingForTest(&timing);
timing.navigation_start = base::Time::FromDoubleT(1);
timing.paint_timing->first_meaningful_paint =
base::TimeDelta::FromMilliseconds(600);
PopulateRequiredTimingFields(&timing);
NavigateAndCommit(GURL(kTestUrl1));
SimulateTimingUpdate(timing);
// Simulate closing the tab.
DeleteContents();
EXPECT_EQ(1ul, test_ukm_recorder().sources_count());
const ukm::UkmSource* source = test_ukm_recorder().GetSourceForUrl(kTestUrl1);
EXPECT_EQ(GURL(kTestUrl1), source->url());
EXPECT_GE(test_ukm_recorder().entries_count(), 1ul);
test_ukm_recorder().ExpectMetric(*source, internal::kUkmPageLoadEventName,
internal::kUkmFirstMeaningfulPaintName, 600);
EXPECT_TRUE(
test_ukm_recorder().HasMetric(*source, internal::kUkmPageLoadEventName,
internal::kUkmForegroundDurationName));
}
TEST_F(UkmPageLoadMetricsObserverTest, MultiplePageLoads) {
page_load_metrics::mojom::PageLoadTiming timing1;
page_load_metrics::InitPageLoadTimingForTest(&timing1);
timing1.navigation_start = base::Time::FromDoubleT(1);
timing1.paint_timing->first_contentful_paint =
base::TimeDelta::FromMilliseconds(200);
PopulateRequiredTimingFields(&timing1);
// Second navigation reports no timing metrics.
page_load_metrics::mojom::PageLoadTiming timing2;
page_load_metrics::InitPageLoadTimingForTest(&timing2);
timing2.navigation_start = base::Time::FromDoubleT(1);
PopulateRequiredTimingFields(&timing2);
NavigateAndCommit(GURL(kTestUrl1));
SimulateTimingUpdate(timing1);
NavigateAndCommit(GURL(kTestUrl2));
SimulateTimingUpdate(timing2);
// Simulate closing the tab.
DeleteContents();
EXPECT_EQ(2ul, test_ukm_recorder().sources_count());
const ukm::UkmSource* source1 =
test_ukm_recorder().GetSourceForUrl(kTestUrl1);
const ukm::UkmSource* source2 =
test_ukm_recorder().GetSourceForUrl(kTestUrl2);
EXPECT_EQ(GURL(kTestUrl1), source1->url());
EXPECT_EQ(GURL(kTestUrl2), source2->url());
EXPECT_NE(source1->id(), source2->id());
EXPECT_GE(test_ukm_recorder().entries_count(), 2ul);
test_ukm_recorder().ExpectMetric(*source1, internal::kUkmPageLoadEventName,
internal::kUkmFirstContentfulPaintName, 200);
EXPECT_FALSE(
test_ukm_recorder().HasMetric(*source2, internal::kUkmPageLoadEventName,
internal::kUkmFirstMeaningfulPaintName));
EXPECT_TRUE(
test_ukm_recorder().HasMetric(*source1, internal::kUkmPageLoadEventName,
internal::kUkmForegroundDurationName));
EXPECT_FALSE(test_ukm_recorder().HasMetric(
*source2, internal::kUkmPageLoadEventName, internal::kUkmParseStartName));
EXPECT_FALSE(
test_ukm_recorder().HasMetric(*source2, internal::kUkmPageLoadEventName,
internal::kUkmFirstContentfulPaintName));
EXPECT_FALSE(
test_ukm_recorder().HasMetric(*source2, internal::kUkmPageLoadEventName,
internal::kUkmFirstMeaningfulPaintName));
EXPECT_TRUE(
test_ukm_recorder().HasMetric(*source2, internal::kUkmPageLoadEventName,
internal::kUkmForegroundDurationName));
}
TEST_F(UkmPageLoadMetricsObserverTest, NetworkQualityEstimates) {
EXPECT_CALL(mock_network_quality_provider(), GetEffectiveConnectionType())
.WillRepeatedly(Return(net::EFFECTIVE_CONNECTION_TYPE_3G));
EXPECT_CALL(mock_network_quality_provider(), GetHttpRTT())
.WillRepeatedly(Return(base::TimeDelta::FromMilliseconds(100)));
EXPECT_CALL(mock_network_quality_provider(), GetTransportRTT())
.WillRepeatedly(Return(base::TimeDelta::FromMilliseconds(200)));
NavigateAndCommit(GURL(kTestUrl1));
// Simulate closing the tab.
DeleteContents();
EXPECT_EQ(1ul, test_ukm_recorder().sources_count());
const ukm::UkmSource* source = test_ukm_recorder().GetSourceForUrl(kTestUrl1);
EXPECT_EQ(GURL(kTestUrl1), source->url());
EXPECT_GE(test_ukm_recorder().entries_count(), 1ul);
test_ukm_recorder().ExpectMetric(
*source, internal::kUkmPageLoadEventName,
internal::kUkmEffectiveConnectionType,
metrics::SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_3G);
test_ukm_recorder().ExpectMetric(*source, internal::kUkmPageLoadEventName,
internal::kUkmHttpRttEstimate, 100);
test_ukm_recorder().ExpectMetric(*source, internal::kUkmPageLoadEventName,
internal::kUkmTransportRttEstimate, 200);
}
TEST_F(UkmPageLoadMetricsObserverTest, PageTransitionReload) {
GURL url(kTestUrl1);
NavigateWithPageTransitionAndCommit(GURL(kTestUrl1),
ui::PAGE_TRANSITION_RELOAD);
// Simulate closing the tab.
DeleteContents();
EXPECT_EQ(1ul, test_ukm_recorder().sources_count());
const ukm::UkmSource* source = test_ukm_recorder().GetSourceForUrl(kTestUrl1);
EXPECT_EQ(GURL(kTestUrl1), source->url());
EXPECT_GE(test_ukm_recorder().entries_count(), 1ul);
test_ukm_recorder().ExpectMetric(*source, internal::kUkmPageLoadEventName,
internal::kUkmPageTransition,
ui::PAGE_TRANSITION_RELOAD);
}