Chromium Code Reviews| Index: components/page_load_metrics/browser/metrics_web_contents_observer.cc |
| diff --git a/components/page_load_metrics/browser/metrics_web_contents_observer.cc b/components/page_load_metrics/browser/metrics_web_contents_observer.cc |
| index ee5219525259945b18b48ba51504e555cb98b2a3..557183f5baadfe65d15a9595d3b39dd79adc227b 100644 |
| --- a/components/page_load_metrics/browser/metrics_web_contents_observer.cc |
| +++ b/components/page_load_metrics/browser/metrics_web_contents_observer.cc |
| @@ -22,6 +22,7 @@ |
| #include "content/public/browser/web_contents_user_data.h" |
| #include "ipc/ipc_message.h" |
| #include "ipc/ipc_message_macros.h" |
| +#include "ui/base/page_transition_types.h" |
| DEFINE_WEB_CONTENTS_USER_DATA_KEY( |
| page_load_metrics::MetricsWebContentsObserver); |
| @@ -81,6 +82,15 @@ void RecordInternalError(InternalErrorLoadEvent event) { |
| UMA_HISTOGRAM_ENUMERATION(kErrorEvents, event, ERR_LAST_ENTRY); |
| } |
| +UserAbortType AbortTypeForPageTransition(ui::PageTransition transition) { |
| + if (ui::PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD)) |
| + return ABORT_RELOAD; |
| + else if (transition & ui::PAGE_TRANSITION_FORWARD_BACK) |
| + return ABORT_FORWARD_BACK; |
| + else |
| + return ABORT_NEW_NAVIGATION; |
| +} |
| + |
| // The number of buckets in the bitfield histogram. These buckets are described |
| // in rappor.xml in PageLoad.CoarseTiming.NavigationToFirstContentfulPaint. |
| // The bucket flag is defined by 1 << bucket_index, and is the bitfield |
| @@ -113,6 +123,7 @@ PageLoadTracker::PageLoadTracker( |
| : renderer_tracked_(false), |
| has_commit_(false), |
| navigation_start_(navigation_handle->NavigationStart()), |
| + abort_type_(ABORT_NONE), |
| started_in_foreground_(in_foreground), |
| embedder_interface_(embedder_interface) { |
| embedder_interface_->RegisterObservers(this); |
| @@ -122,11 +133,9 @@ PageLoadTracker::PageLoadTracker( |
| } |
| PageLoadTracker::~PageLoadTracker() { |
| - if (has_commit_) { |
| - RecordTimingHistograms(); |
| - RecordRappor(); |
| - } |
| PageLoadExtraInfo info = GetPageLoadMetricsInfo(); |
| + RecordRappor(info); |
| + RecordTimingHistograms(info); |
| for (const auto& observer : observers_) { |
| observer->OnComplete(timing_, info); |
| } |
| @@ -196,12 +205,16 @@ void PageLoadTracker::AddObserver( |
| PageLoadExtraInfo PageLoadTracker::GetPageLoadMetricsInfo() { |
| base::TimeDelta first_background_time; |
| base::TimeDelta first_foreground_time; |
| + base::TimeDelta time_to_abort; |
| if (!background_time_.is_null() && started_in_foreground_) |
| first_background_time = background_time_ - navigation_start_; |
| if (!foreground_time_.is_null() && !started_in_foreground_) |
| first_foreground_time = foreground_time_ - navigation_start_; |
| + if (abort_type_ != ABORT_NONE) |
| + time_to_abort = abort_time_ - navigation_start_; |
|
Bryan McQuade
2015/12/10 17:04:23
can we DCHECK that abort_time_ is not zero when ab
Charlie Harrison
2015/12/10 17:59:31
Done.
|
| return PageLoadExtraInfo(first_background_time, first_foreground_time, |
| - started_in_foreground_, has_commit_); |
| + started_in_foreground_, has_commit_, abort_type_, |
| + time_to_abort); |
| } |
| const GURL& PageLoadTracker::committed_url() { |
| @@ -209,40 +222,55 @@ const GURL& PageLoadTracker::committed_url() { |
| return url_; |
| } |
| -// Blink calculates navigation start using TimeTicks, but converts to epoch time |
| -// in its public API. Thus, to compare time values to navigation start, we |
| -// calculate the current time since the epoch using TimeTicks, and convert to |
| -// Time. This method is similar to how blink converts TimeTicks to epoch time. |
| -// There may be slight inaccuracies due to inter-process timestamps, but |
| -// this solution is the best we have right now. |
| -// |
| -// returns a TimeDelta which is |
| -// - Infinity if we were never backgrounded |
| -// - null (TimeDelta()) if we started backgrounded |
| -// - elapsed time to first background if we started in the foreground and |
| -// backgrounded. |
| -base::TimeDelta PageLoadTracker::GetBackgroundDelta() { |
| - if (started_in_foreground_) { |
| - return background_time_.is_null() ? base::TimeDelta::Max() |
| - : background_time_ - navigation_start_; |
| +// We consider the first abort passed here as the true abort, unless it is |
| +// ABORT_OTHER. |
| +void PageLoadTracker::NotifyAbort(UserAbortType abort_type, |
| + const base::TimeTicks& timestamp) { |
| + DCHECK(abort_type != ABORT_NONE); |
| + if (abort_type_ == ABORT_NONE) { |
| + abort_type_ = abort_type; |
| + abort_time_ = timestamp; |
| + } |
| + |
| + // If we got a better signal than ABORT_OTHER in the last 100ms, treat it |
| + // as the cause of the abort (Some ABORT_OTHER signals occur before the true |
| + // signal). Note that this only occurs for provisional loads. While this |
|
Bryan McQuade
2015/12/10 17:04:23
'this only occurs for provisional loads' -> 'abort
Charlie Harrison
2015/12/10 17:59:31
Done.
|
| + // heuristic is coarse, it works better and is simpler than other feasible |
| + // methods. See https://goo.gl/WKRG98. |
|
Bryan McQuade
2015/12/10 17:04:23
navigating to https://goo.gl/WKRG98 requires me to
Charlie Harrison
2015/12/10 17:59:31
Done.
|
| + if (abort_type_ == ABORT_OTHER && abort_type != ABORT_OTHER && |
| + (timestamp - abort_time_).InMilliseconds() < 100) { |
|
Bryan McQuade
2015/12/10 17:04:23
below we have std::min(abort_time_, timestamp); wh
Charlie Harrison
2015/12/10 17:59:31
It is valid for timestamp < abort_time. In this ca
|
| + abort_type_ = abort_type; |
| + abort_time_ = std::min(abort_time_, timestamp); |
| } |
| - return base::TimeDelta(); |
| } |
| -void PageLoadTracker::RecordTimingHistograms() { |
| - DCHECK(has_commit_); |
| - if (!renderer_tracked()) |
| +void PageLoadTracker::RecordTimingHistograms(const PageLoadExtraInfo& info) { |
| + if (!background_time_.is_null() && |
|
Bryan McQuade
2015/12/10 17:04:23
can we use info.first_background_time instead of b
Charlie Harrison
2015/12/10 17:59:31
Done.
|
| + !EventOccurredInForeground(timing_.first_paint, info)) { |
| + if (has_commit_) { |
| + PAGE_LOAD_HISTOGRAM(kHistogramBackgroundBeforePaint, |
| + background_time_ - navigation_start_); |
| + } else { |
| + PAGE_LOAD_HISTOGRAM(kHistogramProvisionalBackground, |
| + background_time_ - navigation_start_); |
| + } |
| + } |
| + |
| + // The rest of the histograms require the load to have commit and be relevant. |
| + if (!has_commit_ || !renderer_tracked()) |
| return; |
| + // The rest of the timing histograms require us to have received IPCs from the |
| + // renderer. Record UMA for how often this occurs (usually for quickly aborted |
| + // loads). For now, don't update observers if this is the case. |
| if (timing_.IsEmpty()) { |
| RecordInternalError(ERR_NO_IPCS_RECEIVED); |
| return; |
| } |
| - base::TimeDelta background_delta = GetBackgroundDelta(); |
| - |
| if (!timing_.dom_content_loaded_event_start.is_zero()) { |
| - if (timing_.dom_content_loaded_event_start < background_delta) { |
| + if (EventOccurredInForeground(timing_.dom_content_loaded_event_start, |
| + info)) { |
| PAGE_LOAD_HISTOGRAM(kHistogramDomContentLoaded, |
| timing_.dom_content_loaded_event_start); |
| } else { |
| @@ -251,7 +279,7 @@ void PageLoadTracker::RecordTimingHistograms() { |
| } |
| } |
| if (!timing_.load_event_start.is_zero()) { |
| - if (timing_.load_event_start < background_delta) { |
| + if (EventOccurredInForeground(timing_.load_event_start, info)) { |
| PAGE_LOAD_HISTOGRAM(kHistogramLoad, timing_.load_event_start); |
| } else { |
| PAGE_LOAD_HISTOGRAM(kBackgroundHistogramLoad, timing_.load_event_start); |
| @@ -261,7 +289,7 @@ void PageLoadTracker::RecordTimingHistograms() { |
| RecordCommittedEvent(RELEVANT_LOAD_FAILED_BEFORE_FIRST_LAYOUT, |
| HasBackgrounded()); |
| } else { |
| - if (timing_.first_layout < background_delta) { |
| + if (EventOccurredInForeground(timing_.first_layout, info)) { |
| PAGE_LOAD_HISTOGRAM(kHistogramFirstLayout, timing_.first_layout); |
| RecordCommittedEvent(RELEVANT_LOAD_SUCCESSFUL_FIRST_LAYOUT, false); |
| } else { |
| @@ -271,14 +299,14 @@ void PageLoadTracker::RecordTimingHistograms() { |
| } |
| } |
| if (!timing_.first_paint.is_zero()) { |
| - if (timing_.first_paint < background_delta) { |
| + if (EventOccurredInForeground(timing_.first_paint, info)) { |
| PAGE_LOAD_HISTOGRAM(kHistogramFirstPaint, timing_.first_paint); |
| } else { |
| PAGE_LOAD_HISTOGRAM(kBackgroundHistogramFirstPaint, timing_.first_paint); |
| } |
| } |
| if (!timing_.first_text_paint.is_zero()) { |
| - if (timing_.first_text_paint < background_delta) { |
| + if (EventOccurredInForeground(timing_.first_text_paint, info)) { |
| PAGE_LOAD_HISTOGRAM(kHistogramFirstTextPaint, timing_.first_text_paint); |
| } else { |
| PAGE_LOAD_HISTOGRAM(kBackgroundHistogramFirstTextPaint, |
| @@ -286,7 +314,7 @@ void PageLoadTracker::RecordTimingHistograms() { |
| } |
| } |
| if (!timing_.first_image_paint.is_zero()) { |
| - if (timing_.first_image_paint < background_delta) { |
| + if (EventOccurredInForeground(timing_.first_image_paint, info)) { |
| PAGE_LOAD_HISTOGRAM(kHistogramFirstImagePaint, timing_.first_image_paint); |
| } else { |
| PAGE_LOAD_HISTOGRAM(kBackgroundHistogramFirstImagePaint, |
| @@ -295,7 +323,7 @@ void PageLoadTracker::RecordTimingHistograms() { |
| } |
| base::TimeDelta first_contentful_paint = GetFirstContentfulPaint(timing_); |
| if (!first_contentful_paint.is_zero()) { |
| - if (first_contentful_paint < background_delta) { |
| + if (EventOccurredInForeground(first_contentful_paint, info)) { |
| PAGE_LOAD_HISTOGRAM(kHistogramFirstContentfulPaint, |
| first_contentful_paint); |
| // Bucket these histograms into high/low resolution clock systems. This |
| @@ -316,7 +344,8 @@ void PageLoadTracker::RecordTimingHistograms() { |
| // Log time to first foreground / time to first background. Log counts that we |
| // started a relevant page load in the foreground / background. |
| if (!background_time_.is_null()) { |
| - PAGE_LOAD_HISTOGRAM(kHistogramFirstBackground, background_delta); |
| + PAGE_LOAD_HISTOGRAM(kHistogramFirstBackground, |
| + background_time_ - navigation_start_); |
| } else if (!foreground_time_.is_null()) { |
| PAGE_LOAD_HISTOGRAM(kHistogramFirstForeground, |
| foreground_time_ - navigation_start_); |
| @@ -347,7 +376,9 @@ void PageLoadTracker::RecordCommittedEvent(CommittedRelevantLoadEvent event, |
| } |
| } |
| -void PageLoadTracker::RecordRappor() { |
| +void PageLoadTracker::RecordRappor(const PageLoadExtraInfo& info) { |
| + if (!info.has_commit) |
| + return; |
| DCHECK(!committed_url().is_empty()); |
| rappor::RapporService* rappor_service = |
| embedder_interface_->GetRapporService(); |
| @@ -355,8 +386,7 @@ void PageLoadTracker::RecordRappor() { |
| return; |
| base::TimeDelta first_contentful_paint = GetFirstContentfulPaint(timing_); |
| // Log the eTLD+1 of sites that show poor loading performance. |
| - if (!first_contentful_paint.is_zero() && |
| - first_contentful_paint < GetBackgroundDelta()) { |
| + if (EventOccurredInForeground(first_contentful_paint, info)) { |
| scoped_ptr<rappor::Sample> sample = |
| rappor_service->CreateSample(rappor::UMA_RAPPOR_TYPE); |
| sample->SetStringField( |
| @@ -394,7 +424,9 @@ MetricsWebContentsObserver* MetricsWebContentsObserver::CreateForWebContents( |
| return metrics; |
| } |
| -MetricsWebContentsObserver::~MetricsWebContentsObserver() {} |
| +MetricsWebContentsObserver::~MetricsWebContentsObserver() { |
| + NotifyAbortAllLoads(ABORT_CLOSE); |
| +} |
| bool MetricsWebContentsObserver::OnMessageReceived( |
| const IPC::Message& message, |
| @@ -445,12 +477,18 @@ void MetricsWebContentsObserver::DidFinishNavigation( |
| // Handle a pre-commit error here. Navigations that result in an error page |
| // will be ignored. Note that downloads/204s will result in HasCommitted() |
| // returning false. |
| + // TODO(csharrison): Track changes to NavigationHandle for signals when this |
| + // is the case (HTTP response headers). |
| if (!navigation_handle->HasCommitted()) { |
| net::Error error = navigation_handle->GetNetErrorCode(); |
| - finished_nav->RecordProvisionalEvent( |
| - error == net::OK ? PROVISIONAL_LOAD_STOPPED |
| + ProvisionalLoadEvent event = error == net::OK ? PROVISIONAL_LOAD_STOPPED |
| : error == net::ERR_ABORTED ? PROVISIONAL_LOAD_ERR_ABORTED |
| - : PROVISIONAL_LOAD_ERR_FAILED_NON_ABORT); |
| + : PROVISIONAL_LOAD_ERR_FAILED_NON_ABORT; |
| + finished_nav->RecordProvisionalEvent(event); |
| + if (event != PROVISIONAL_LOAD_ERR_FAILED_NON_ABORT) { |
| + finished_nav->NotifyAbort(ABORT_OTHER, base::TimeTicks::Now()); |
| + aborted_provisional_loads_.push_back(std::move(finished_nav)); |
| + } |
| return; |
| } |
| finished_nav->RecordProvisionalEvent(PROVISIONAL_LOAD_COMMITTED); |
| @@ -459,7 +497,17 @@ void MetricsWebContentsObserver::DidFinishNavigation( |
| if (navigation_handle->IsSamePage()) |
| return; |
| + // Notify other loads that they may have been aborted by this committed load. |
| + // Note that by using the committed navigation start as the abort cause, we |
| + // lose data on provisional loads that were aborted by other provisional |
| + // loads. Those will either be listed as ABORT_OTHER or as being aborted by |
| + // this load. |
| + NotifyAbortAllLoadsWithTimestamp( |
| + AbortTypeForPageTransition(navigation_handle->GetPageTransition()), |
| + navigation_handle->NavigationStart()); |
| + |
| committed_load_ = finished_nav.Pass(); |
| + aborted_provisional_loads_.clear(); |
| const GURL& browser_url = web_contents()->GetLastCommittedURL(); |
| const std::string& mime_type = web_contents()->GetContentsMimeType(); |
| @@ -471,6 +519,10 @@ void MetricsWebContentsObserver::DidFinishNavigation( |
| committed_load_->Commit(navigation_handle); |
| } |
| +void MetricsWebContentsObserver::NavigationStopped() { |
| + NotifyAbortAllLoads(ABORT_STOP); |
| +} |
| + |
| void MetricsWebContentsObserver::DidRedirectNavigation( |
| content::NavigationHandle* navigation_handle) { |
| if (!navigation_handle->IsInMainFrame()) |
| @@ -504,7 +556,27 @@ void MetricsWebContentsObserver::WasHidden() { |
| // we have one. |
| void MetricsWebContentsObserver::RenderProcessGone( |
| base::TerminationStatus status) { |
| + NotifyAbortAllLoads(ABORT_CLOSE); |
| committed_load_.reset(); |
| + provisional_loads_.clear(); |
| + aborted_provisional_loads_.clear(); |
| +} |
| + |
| +void MetricsWebContentsObserver::NotifyAbortAllLoads(UserAbortType abort_type) { |
| + NotifyAbortAllLoadsWithTimestamp(abort_type, base::TimeTicks::Now()); |
| +} |
| + |
| +void MetricsWebContentsObserver::NotifyAbortAllLoadsWithTimestamp( |
| + UserAbortType abort_type, |
| + const base::TimeTicks& timestamp) { |
| + if (committed_load_) |
| + committed_load_->NotifyAbort(abort_type, timestamp); |
| + for (const auto& kv : provisional_loads_) { |
| + kv.second->NotifyAbort(abort_type, timestamp); |
| + } |
| + for (const auto& plt : aborted_provisional_loads_) { |
| + plt->NotifyAbort(abort_type, timestamp); |
|
Bryan McQuade
2015/12/10 17:04:23
I'm finding following the logic in NotifyAbort a b
Charlie Harrison
2015/12/10 17:59:31
This isn't quite true. We can notify aborts multip
|
| + } |
| } |
| void MetricsWebContentsObserver::OnTimingUpdated( |