| // 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()); |
| } |