| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/renderer_host/navigation_throttle_runner.h" |
| |
| #include "base/check_deref.h" |
| #include "base/debug/crash_logging.h" |
| #include "base/feature_list.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/metrics_hashes.h" |
| #include "base/strings/strcat.h" |
| #include "base/trace_event/trace_event.h" |
| #include "build/build_config.h" |
| #include "services/metrics/public/cpp/ukm_builders.h" |
| #include "services/metrics/public/cpp/ukm_source_id.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| NavigationThrottle::ThrottleCheckResult ExecuteNavigationEvent( |
| NavigationThrottle* throttle, |
| NavigationThrottleEvent event) { |
| switch (event) { |
| case NavigationThrottleEvent::kNoEvent: |
| DUMP_WILL_BE_NOTREACHED(); |
| return NavigationThrottle::CANCEL_AND_IGNORE; |
| case NavigationThrottleEvent::kWillStartRequest: |
| return throttle->WillStartRequest(); |
| case NavigationThrottleEvent::kWillRedirectRequest: |
| return throttle->WillRedirectRequest(); |
| case NavigationThrottleEvent::kWillFailRequest: |
| return throttle->WillFailRequest(); |
| case NavigationThrottleEvent::kWillProcessResponse: |
| return throttle->WillProcessResponse(); |
| case NavigationThrottleEvent::kWillCommitWithoutUrlLoader: |
| return throttle->WillCommitWithoutUrlLoader(); |
| } |
| NOTREACHED(); |
| } |
| |
| perfetto::StaticString GetEventName(NavigationThrottleEvent event) { |
| switch (event) { |
| case NavigationThrottleEvent::kNoEvent: |
| DUMP_WILL_BE_NOTREACHED(); |
| return ""; |
| case NavigationThrottleEvent::kWillStartRequest: |
| return "NavigationThrottle::WillStartRequest"; |
| case NavigationThrottleEvent::kWillRedirectRequest: |
| return "NavigationThrottle::WillRedirectRequest"; |
| case NavigationThrottleEvent::kWillFailRequest: |
| return "NavigationThrottle::WillFailRequest"; |
| case NavigationThrottleEvent::kWillProcessResponse: |
| return "NavigationThrottle::WillProcessResponse"; |
| case NavigationThrottleEvent::kWillCommitWithoutUrlLoader: |
| return "NavigationThrottle::WillCommitWithoutUrlLoader"; |
| } |
| NOTREACHED(); |
| } |
| |
| const char* GetEventNameForHistogram(NavigationThrottleEvent event) { |
| switch (event) { |
| case NavigationThrottleEvent::kNoEvent: |
| DUMP_WILL_BE_NOTREACHED(); |
| return ""; |
| case NavigationThrottleEvent::kWillStartRequest: |
| return "WillStartRequest"; |
| case NavigationThrottleEvent::kWillRedirectRequest: |
| return "WillRedirectRequest"; |
| case NavigationThrottleEvent::kWillFailRequest: |
| return "WillFailRequest"; |
| case NavigationThrottleEvent::kWillProcessResponse: |
| return "WillProcessResponse"; |
| case NavigationThrottleEvent::kWillCommitWithoutUrlLoader: |
| return "WillCommitWithoutUrlLoader"; |
| } |
| NOTREACHED(); |
| } |
| |
| base::TimeDelta RecordHistogram(NavigationThrottleEvent event, |
| base::Time start, |
| const std::string& metric_type) { |
| base::TimeDelta delta = base::Time::Now() - start; |
| base::UmaHistogramTimes(base::StrCat({"Navigation.Throttle", metric_type, ".", |
| GetEventNameForHistogram(event)}), |
| delta); |
| return delta; |
| } |
| |
| base::TimeDelta RecordDeferTimeHistogram(NavigationThrottleEvent event, |
| base::Time start) { |
| return RecordHistogram(event, start, "DeferTime"); |
| } |
| |
| void RecordExecutionTimeHistogram(NavigationThrottleEvent event, |
| base::Time start) { |
| RecordHistogram(event, start, "ExecutionTime"); |
| } |
| |
| } // namespace |
| |
| NavigationThrottleRunner::NavigationThrottleRunner( |
| NavigationThrottleRegistryBase* registry, |
| int64_t navigation_id, |
| bool is_primary_main_frame) |
| : registry_(CHECK_DEREF(registry)), |
| navigation_id_(navigation_id), |
| is_primary_main_frame_(is_primary_main_frame) {} |
| |
| NavigationThrottleRunner::~NavigationThrottleRunner() { |
| base::UmaHistogramTimes("Navigation.ThrottleTotalDeferTime", |
| total_defer_duration_time_); |
| base::UmaHistogramCounts100("Navigation.ThrottleTotalDeferCount", |
| defer_count_); |
| base::UmaHistogramTimes("Navigation.ThrottleTotalDeferTime.Request", |
| total_defer_duration_time_for_request_); |
| base::UmaHistogramCounts100("Navigation.ThrottleTotalDeferCount.Request", |
| defer_count_for_request_); |
| } |
| |
| void NavigationThrottleRunner::ProcessNavigationEvent( |
| NavigationThrottleEvent event) { |
| CHECK_NE(NavigationThrottleEvent::kNoEvent, event); |
| current_event_ = event; |
| next_index_ = 0; |
| ProcessInternal(); |
| } |
| |
| void NavigationThrottleRunner::ResumeProcessingNavigationEvent( |
| NavigationThrottle* deferring_throttle) { |
| base::TimeDelta defer_time = |
| RecordDeferTimeHistogram(current_event_, defer_start_time_); |
| total_defer_duration_time_ += defer_time; |
| defer_count_++; |
| if (current_event_ == NavigationThrottleEvent::kWillStartRequest || |
| current_event_ == NavigationThrottleEvent::kWillRedirectRequest) { |
| total_defer_duration_time_for_request_ += defer_time; |
| defer_count_for_request_++; |
| } |
| base::UmaHistogramEnumeration("Navigation.ThrottleDeferredEvent", |
| current_event_); |
| RecordDeferTimeUKM(); |
| ProcessInternal(); |
| } |
| |
| void NavigationThrottleRunner::ProcessInternal() { |
| TRACE_EVENT0("navigation", "NavigationThrottleRunner::ProcessInternal"); |
| CHECK_NE(NavigationThrottleEvent::kNoEvent, current_event_); |
| base::Time start_time = base::Time::Now(); |
| if (!event_process_start_time_.has_value()) { |
| event_process_start_time_ = start_time; |
| event_process_execution_time_ = base::TimeDelta(); |
| } |
| base::WeakPtr<NavigationThrottleRunner> weak_ref = weak_factory_.GetWeakPtr(); |
| |
| // Capture into a local variable the |navigation_id_| value, since this |
| // object can be freed by any of the throttles being invoked and the trace |
| // events need to be able to use the navigation id safely in such a case. |
| int64_t local_navigation_id = navigation_id_; |
| |
| auto& throttles = registry_->GetThrottles(); |
| for (size_t i = next_index_; i < throttles.size(); ++i) { |
| TRACE_EVENT0("navigation", |
| "NavigationThrottleRunner::ProcessInternal.loop"); |
| TRACE_EVENT_BEGIN("navigation", GetEventName(current_event_), |
| perfetto::Track::Global(local_navigation_id), "throttle", |
| throttles[i]->GetNameForLogging()); |
| |
| base::Time start = base::Time::Now(); |
| NavigationThrottle::ThrottleCheckResult result = |
| ExecuteNavigationEvent(throttles[i].get(), current_event_); |
| if (!weak_ref) { |
| // The NavigationThrottle execution has destroyed this |
| // NavigationThrottleRunner. Return immediately. |
| // GetEventName(current_event_) |
| TRACE_EVENT_END("navigation", |
| perfetto::Track::Global(local_navigation_id), "result", |
| "deleted"); |
| return; |
| } |
| RecordExecutionTimeHistogram(current_event_, start); |
| // GetEventName(current_event_) |
| TRACE_EVENT_END("navigation", perfetto::Track::Global(local_navigation_id), |
| "result", result.action()); |
| |
| switch (result.action()) { |
| case NavigationThrottle::PROCEED: |
| continue; |
| |
| case NavigationThrottle::BLOCK_REQUEST_AND_COLLAPSE: |
| case NavigationThrottle::BLOCK_REQUEST: |
| case NavigationThrottle::BLOCK_RESPONSE: |
| case NavigationThrottle::CANCEL: |
| case NavigationThrottle::CANCEL_AND_IGNORE: |
| next_index_ = 0; |
| event_process_start_time_.reset(); |
| InformRegistry(result); |
| return; |
| |
| case NavigationThrottle::DEFER: |
| registry_->OnDeferProcessingNavigationEvent(throttles[i].get()); |
| next_index_ = i + 1; |
| defer_start_time_ = base::Time::Now(); |
| event_process_execution_time_ += base::Time::Now() - start_time; |
| return; |
| } |
| } |
| |
| base::Time end_time = base::Time::Now(); |
| event_process_execution_time_ += end_time - start_time; |
| base::UmaHistogramTimes( |
| base::StrCat({"Navigation.ThrottleEventExecutionTime.", |
| GetEventNameForHistogram(current_event_)}), |
| event_process_execution_time_); |
| base::UmaHistogramTimes( |
| base::StrCat({"Navigation.ThrottleEventDurationTime.", |
| GetEventNameForHistogram(current_event_)}), |
| end_time - *event_process_start_time_); |
| event_process_start_time_.reset(); |
| next_index_ = 0; |
| InformRegistry(NavigationThrottle::PROCEED); |
| } |
| |
| void NavigationThrottleRunner::InformRegistry( |
| const NavigationThrottle::ThrottleCheckResult& result) { |
| // Now that the event has executed, reset the current event to kNoEvent since |
| // we're no longer processing any event. Do it before the call to the |
| // delegate, as it might lead to the deletion of this |
| // NavigationThrottleRunner. |
| NavigationThrottleEvent event = current_event_; |
| current_event_ = NavigationThrottleEvent::kNoEvent; |
| registry_->OnEventProcessed(event, result); |
| // DO NOT ADD CODE AFTER THIS. The NavigationThrottleRunner might have been |
| // deleted by the previous call. |
| } |
| |
| void NavigationThrottleRunner::RecordDeferTimeUKM() { |
| if (!is_primary_main_frame_ || registry_->GetDeferringThrottles().empty()) { |
| return; |
| } |
| ukm::builders::NavigationThrottleDeferredTime builder( |
| ukm::ConvertToSourceId(navigation_id_, ukm::SourceIdType::NAVIGATION_ID)); |
| builder.SetDurationOfNavigationDeferralMs( |
| (base::Time::Now() - defer_start_time_).InMilliseconds()); |
| builder.SetNavigationThrottleEventType(static_cast<int64_t>(current_event_)); |
| // The logging name is converted to an MD5 int64_t hash which is recorded in |
| // UKM. The possible values are sparse, and analyses should hash the values |
| // returned by NavigationThrottle::GetNameForLogging to determine which |
| // throttle deferred the navigation. |
| builder.SetNavigationThrottleNameHash(base::HashMetricName( |
| (*registry_->GetDeferringThrottles().cbegin())->GetNameForLogging())); |
| builder.Record(ukm::UkmRecorder::Get()); |
| } |
| |
| } // namespace content |