blob: 7359e104f467beecb24e7f1dff695b3195ee243e [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 <memory>
#include "chrome/browser/browser_process.h"
#include "chrome/browser/page_load_metrics/page_load_metrics_util.h"
#include "chrome/browser/profiles/profile.h"
#include "components/metrics/net/network_metrics_provider.h"
#include "services/metrics/public/cpp/metrics_utils.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "services/network/public/cpp/network_quality_tracker.h"
#include "third_party/metrics_proto/system_profile.pb.h"
// static
std::unique_ptr<page_load_metrics::PageLoadMetricsObserver>
UkmPageLoadMetricsObserver::CreateIfNeeded() {
if (!ukm::UkmRecorder::Get()) {
return nullptr;
}
return std::make_unique<UkmPageLoadMetricsObserver>(
g_browser_process->network_quality_tracker());
}
UkmPageLoadMetricsObserver::UkmPageLoadMetricsObserver(
network::NetworkQualityTracker* network_quality_tracker)
: network_quality_tracker_(network_quality_tracker) {
DCHECK(network_quality_tracker_);
}
UkmPageLoadMetricsObserver::~UkmPageLoadMetricsObserver() = default;
UkmPageLoadMetricsObserver::ObservePolicy UkmPageLoadMetricsObserver::OnStart(
content::NavigationHandle* navigation_handle,
const GURL& currently_committed_url,
bool started_in_foreground) {
if (!started_in_foreground)
return STOP_OBSERVING;
// When OnStart is invoked, we don't yet know whether we're observing a web
// page load, vs another kind of load (e.g. a download or a PDF). Thus,
// metrics and source information should not be recorded here. Instead, we
// store data we might want to persist in member variables below, and later
// record UKM metrics for that data once we've confirmed that we're observing
// a web page load.
effective_connection_type_ =
network_quality_tracker_->GetEffectiveConnectionType();
http_rtt_estimate_ = network_quality_tracker_->GetHttpRTT();
transport_rtt_estimate_ = network_quality_tracker_->GetTransportRTT();
downstream_kbps_estimate_ =
network_quality_tracker_->GetDownstreamThroughputKbps();
page_transition_ = navigation_handle->GetPageTransition();
return CONTINUE_OBSERVING;
}
UkmPageLoadMetricsObserver::ObservePolicy UkmPageLoadMetricsObserver::OnCommit(
content::NavigationHandle* navigation_handle,
ukm::SourceId source_id) {
// The PageTransition for the navigation may be updated on commit.
page_transition_ = navigation_handle->GetPageTransition();
return CONTINUE_OBSERVING;
}
UkmPageLoadMetricsObserver::ObservePolicy
UkmPageLoadMetricsObserver::FlushMetricsOnAppEnterBackground(
const page_load_metrics::mojom::PageLoadTiming& timing,
const page_load_metrics::PageLoadExtraInfo& info) {
RecordPageLoadExtraInfoMetrics(info, base::TimeTicks::Now());
RecordTimingMetrics(timing, info.source_id);
return STOP_OBSERVING;
}
UkmPageLoadMetricsObserver::ObservePolicy UkmPageLoadMetricsObserver::OnHidden(
const page_load_metrics::mojom::PageLoadTiming& timing,
const page_load_metrics::PageLoadExtraInfo& info) {
RecordPageLoadExtraInfoMetrics(
info, base::TimeTicks() /* no app_background_time */);
RecordTimingMetrics(timing, info.source_id);
return STOP_OBSERVING;
}
void UkmPageLoadMetricsObserver::OnFailedProvisionalLoad(
const page_load_metrics::FailedProvisionalLoadInfo& failed_load_info,
const page_load_metrics::PageLoadExtraInfo& extra_info) {
RecordPageLoadExtraInfoMetrics(
extra_info, base::TimeTicks() /* no app_background_time */);
// Error codes have negative values, however we log net error code enum values
// for UMA histograms using the equivalent positive value. For consistency in
// UKM, we convert to a positive value here.
int64_t net_error_code = static_cast<int64_t>(failed_load_info.error) * -1;
DCHECK_GE(net_error_code, 0);
ukm::builders::PageLoad(extra_info.source_id)
.SetNet_ErrorCode_OnFailedProvisionalLoad(net_error_code)
.SetPageTiming_NavigationToFailedProvisionalLoad(
failed_load_info.time_to_failed_provisional_load.InMilliseconds())
.Record(ukm::UkmRecorder::Get());
}
void UkmPageLoadMetricsObserver::OnComplete(
const page_load_metrics::mojom::PageLoadTiming& timing,
const page_load_metrics::PageLoadExtraInfo& info) {
RecordPageLoadExtraInfoMetrics(
info, base::TimeTicks() /* no app_background_time */);
RecordTimingMetrics(timing, info.source_id);
}
void UkmPageLoadMetricsObserver::OnLoadedResource(
const page_load_metrics::ExtraRequestCompleteInfo&
extra_request_complete_info) {
if (extra_request_complete_info.was_cached) {
cache_bytes_ += extra_request_complete_info.raw_body_bytes;
} else {
network_bytes_ += extra_request_complete_info.raw_body_bytes;
}
}
void UkmPageLoadMetricsObserver::RecordTimingMetrics(
const page_load_metrics::mojom::PageLoadTiming& timing,
ukm::SourceId source_id) {
ukm::builders::PageLoad builder(source_id);
if (timing.parse_timing->parse_start) {
builder.SetParseTiming_NavigationToParseStart(
timing.parse_timing->parse_start.value().InMilliseconds());
}
if (timing.document_timing->dom_content_loaded_event_start) {
builder.SetDocumentTiming_NavigationToDOMContentLoadedEventFired(
timing.document_timing->dom_content_loaded_event_start.value()
.InMilliseconds());
}
if (timing.document_timing->load_event_start) {
builder.SetDocumentTiming_NavigationToLoadEventFired(
timing.document_timing->load_event_start.value().InMilliseconds());
}
if (timing.paint_timing->first_paint) {
builder.SetPaintTiming_NavigationToFirstPaint(
timing.paint_timing->first_paint.value().InMilliseconds());
}
if (timing.paint_timing->first_contentful_paint) {
builder.SetPaintTiming_NavigationToFirstContentfulPaint(
timing.paint_timing->first_contentful_paint.value().InMilliseconds());
}
if (timing.paint_timing->first_meaningful_paint) {
builder.SetExperimental_PaintTiming_NavigationToFirstMeaningfulPaint(
timing.paint_timing->first_meaningful_paint.value().InMilliseconds());
}
if (timing.interactive_timing->interactive) {
base::TimeDelta time_to_interactive =
timing.interactive_timing->interactive.value();
if (!timing.interactive_timing->first_invalidating_input ||
timing.interactive_timing->first_invalidating_input.value() >
time_to_interactive) {
builder.SetExperimental_NavigationToInteractive(
time_to_interactive.InMilliseconds());
}
}
if (timing.interactive_timing->first_input_delay) {
base::TimeDelta first_input_delay =
timing.interactive_timing->first_input_delay.value();
builder.SetInteractiveTiming_FirstInputDelay(
first_input_delay.InMilliseconds());
}
if (timing.interactive_timing->first_input_timestamp) {
base::TimeDelta first_input_timestamp =
timing.interactive_timing->first_input_timestamp.value();
builder.SetInteractiveTiming_FirstInputTimestamp(
first_input_timestamp.InMilliseconds());
}
if (timing.interactive_timing->longest_input_delay) {
base::TimeDelta longest_input_delay =
timing.interactive_timing->longest_input_delay.value();
builder.SetInteractiveTiming_LongestInputDelay(
longest_input_delay.InMilliseconds());
}
if (timing.interactive_timing->longest_input_timestamp) {
base::TimeDelta longest_input_timestamp =
timing.interactive_timing->longest_input_timestamp.value();
builder.SetInteractiveTiming_LongestInputTimestamp(
longest_input_timestamp.InMilliseconds());
}
// Use a bucket spacing factor of 1.3 for bytes.
builder.SetNet_CacheBytes(ukm::GetExponentialBucketMin(cache_bytes_, 1.3));
builder.SetNet_NetworkBytes(
ukm::GetExponentialBucketMin(network_bytes_, 1.3));
builder.Record(ukm::UkmRecorder::Get());
}
void UkmPageLoadMetricsObserver::RecordPageLoadExtraInfoMetrics(
const page_load_metrics::PageLoadExtraInfo& info,
base::TimeTicks app_background_time) {
ukm::builders::PageLoad builder(info.source_id);
base::Optional<base::TimeDelta> foreground_duration =
page_load_metrics::GetInitialForegroundDuration(info,
app_background_time);
if (foreground_duration) {
builder.SetPageTiming_ForegroundDuration(
foreground_duration.value().InMilliseconds());
}
// Convert to the EffectiveConnectionType as used in SystemProfileProto
// before persisting the metric.
metrics::SystemProfileProto::Network::EffectiveConnectionType
proto_effective_connection_type =
metrics::ConvertEffectiveConnectionType(effective_connection_type_);
if (proto_effective_connection_type !=
metrics::SystemProfileProto::Network::EFFECTIVE_CONNECTION_TYPE_UNKNOWN) {
builder.SetNet_EffectiveConnectionType2_OnNavigationStart(
static_cast<int64_t>(proto_effective_connection_type));
}
if (http_rtt_estimate_) {
builder.SetNet_HttpRttEstimate_OnNavigationStart(
static_cast<int64_t>(http_rtt_estimate_.value().InMilliseconds()));
}
if (transport_rtt_estimate_) {
builder.SetNet_TransportRttEstimate_OnNavigationStart(
static_cast<int64_t>(transport_rtt_estimate_.value().InMilliseconds()));
}
if (downstream_kbps_estimate_) {
builder.SetNet_DownstreamKbpsEstimate_OnNavigationStart(
static_cast<int64_t>(downstream_kbps_estimate_.value()));
}
// page_transition_ fits in a uint32_t, so we can safely cast to int64_t.
builder.SetNavigation_PageTransition(static_cast<int64_t>(page_transition_));
builder.Record(ukm::UkmRecorder::Get());
}