| // Copyright 2021 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/preloading/prerender/prerender_metrics.h" |
| |
| #include <cmath> |
| #include <memory> |
| #include <optional> |
| #include <variant> |
| |
| #include "base/check_op.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/metrics_hashes.h" |
| #include "base/strings/string_util.h" |
| #include "content/browser/devtools/devtools_instrumentation.h" |
| #include "content/browser/preloading/preloading_trigger_type_impl.h" |
| #include "content/browser/preloading/prerender/prerender_final_status.h" |
| #include "content/browser/preloading/prerender/prerender_host.h" |
| #include "content/public/browser/preloading_trigger_type.h" |
| #include "services/metrics/public/cpp/ukm_builders.h" |
| #include "services/metrics/public/cpp/ukm_recorder.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| // Do not add new value. |
| // These values are used to persists sparse metrics to logs. |
| enum HeaderMismatchType : uint32_t { |
| kMatch = 0, |
| kMissingInPrerendering = 1, |
| kMissingInActivation = 2, |
| kValueMismatch = 3, |
| kMaxValue = kValueMismatch |
| }; |
| |
| PrerenderCancelledInterface GetCancelledInterfaceType( |
| const std::string& interface_name) { |
| if (interface_name == "device.mojom.GamepadHapticsManager") |
| return PrerenderCancelledInterface::kGamepadHapticsManager; |
| else if (interface_name == "device.mojom.GamepadMonitor") |
| return PrerenderCancelledInterface::kGamepadMonitor; |
| else if (interface_name == |
| "chrome.mojom.TrustedVaultEncryptionKeysExtension") { |
| return PrerenderCancelledInterface::kTrustedVaultEncryptionKeys; |
| } |
| return PrerenderCancelledInterface::kUnknown; |
| } |
| |
| int32_t InterfaceNameHasher(const std::string& interface_name) { |
| return static_cast<int32_t>(base::HashMetricNameAs32Bits(interface_name)); |
| } |
| |
| int32_t HeaderMismatchHasher(const std::string& header, |
| HeaderMismatchType mismatch_type) { |
| // Throw two bits away to encode the mismatch type. |
| // {0---30} bits are the encoded hash number. |
| // {31, 32} bits encode the mismatch type. |
| static_assert(HeaderMismatchType::kMaxValue == 3u, |
| "HeaderMismatchType should use 2 bits at most."); |
| return static_cast<int32_t>(base::HashMetricNameAs32Bits(header) << 2 | |
| mismatch_type); |
| } |
| |
| std::string GenerateHistogramName(const std::string& histogram_base_name, |
| PreloadingTriggerType trigger_type, |
| const std::string& embedder_suffix) { |
| return histogram_base_name + |
| GeneratePrerenderHistogramSuffix(trigger_type, embedder_suffix); |
| } |
| |
| void ReportHeaderMismatch(const std::string& key, |
| HeaderMismatchType mismatch_type, |
| const std::string& histogram_suffix) { |
| base::UmaHistogramSparse( |
| "Prerender.Experimental.ActivationHeadersMismatch" + histogram_suffix, |
| HeaderMismatchHasher(base::ToLowerASCII(key), mismatch_type)); |
| } |
| |
| void ReportAllPrerenderMismatchedHeaders( |
| const std::vector<PrerenderMismatchedHeaders>& mismatched_headers, |
| const std::string& histogram_suffix) { |
| for (const auto& mismatched_header : mismatched_headers) { |
| if (mismatched_header.initial_value.has_value() && |
| mismatched_header.activation_value.has_value()) { |
| ReportHeaderMismatch(mismatched_header.header_name, |
| HeaderMismatchType::kValueMismatch, |
| histogram_suffix); |
| } else if (mismatched_header.initial_value.has_value()) { |
| ReportHeaderMismatch(mismatched_header.header_name, |
| HeaderMismatchType::kMissingInActivation, |
| histogram_suffix); |
| } else { |
| ReportHeaderMismatch(mismatched_header.header_name, |
| HeaderMismatchType::kMissingInPrerendering, |
| histogram_suffix); |
| } |
| } |
| } |
| |
| // Called by MojoBinderPolicyApplier. This function records the Mojo interface |
| // that causes MojoBinderPolicyApplier to cancel prerendering. |
| void RecordPrerenderCancelledInterface(const std::string& interface_name, |
| const std::string& histogram_suffix) { |
| const PrerenderCancelledInterface interface_type = |
| GetCancelledInterfaceType(interface_name); |
| base::UmaHistogramEnumeration( |
| "Prerender.Experimental.PrerenderCancelledInterface" + histogram_suffix, |
| interface_type); |
| if (interface_type == PrerenderCancelledInterface::kUnknown) { |
| // These interfaces can be required by embedders, or not set to kCancel |
| // expclitly, e.g., channel-associated interfaces. Record these interfaces |
| // with the sparse histogram to ensure all of them are tracked. |
| base::UmaHistogramSparse( |
| "Prerender.Experimental.PrerenderCancelledUnknownInterface" + |
| histogram_suffix, |
| InterfaceNameHasher(interface_name)); |
| } |
| } |
| |
| void RecordPrerenderFinalStatusUma( |
| PrerenderFinalStatus final_status, |
| PreloadingTriggerType trigger_type, |
| const std::string& embedder_histogram_suffix) { |
| base::UmaHistogramEnumeration( |
| GenerateHistogramName("Prerender.Experimental.PrerenderHostFinalStatus", |
| trigger_type, embedder_histogram_suffix), |
| final_status); |
| } |
| |
| void RecordDidFailLoadErrorType(int32_t error_code, |
| const std::string& histogram_suffix) { |
| base::UmaHistogramSparse( |
| "Prerender.Experimental.PrerenderLoadingFailureError" + histogram_suffix, |
| std::abs(error_code)); |
| } |
| |
| } // namespace |
| |
| // static |
| PrerenderCancellationReason |
| PrerenderCancellationReason::BuildForDisallowActivationState( |
| uint64_t disallow_activation_reason) { |
| return PrerenderCancellationReason( |
| PrerenderFinalStatus::kInactivePageRestriction, |
| disallow_activation_reason); |
| } |
| |
| // static |
| PrerenderCancellationReason |
| PrerenderCancellationReason::BuildForMojoBinderPolicy( |
| const std::string& interface_name) { |
| return PrerenderCancellationReason(PrerenderFinalStatus::kMojoBinderPolicy, |
| interface_name); |
| } |
| |
| const std::vector<PrerenderMismatchedHeaders>* |
| PrerenderCancellationReason::GetPrerenderMismatchedHeaders() const { |
| return std::get_if<std::vector<PrerenderMismatchedHeaders>>(&explanation_); |
| } |
| |
| // static |
| PrerenderCancellationReason PrerenderCancellationReason:: |
| CreateCandidateReasonForActivationParameterMismatch() { |
| return PrerenderCancellationReason( |
| PrerenderFinalStatus::kActivationNavigationParameterMismatch); |
| } |
| |
| void PrerenderCancellationReason::SetPrerenderMismatchedHeaders( |
| std::unique_ptr<std::vector<PrerenderMismatchedHeaders>> |
| mismatched_headers) { |
| explanation_ = std::move(*mismatched_headers); |
| } |
| |
| // static |
| PrerenderCancellationReason PrerenderCancellationReason::BuildForLoadingError( |
| int32_t error_code) { |
| return PrerenderCancellationReason(PrerenderFinalStatus::kDidFailLoad, |
| error_code); |
| } |
| |
| PrerenderCancellationReason::PrerenderCancellationReason( |
| PrerenderFinalStatus final_status) |
| : PrerenderCancellationReason(final_status, DetailedReasonVariant()) {} |
| |
| PrerenderCancellationReason::PrerenderCancellationReason( |
| PrerenderCancellationReason&& reason) = default; |
| |
| PrerenderCancellationReason::~PrerenderCancellationReason() = default; |
| |
| PrerenderCancellationReason::PrerenderCancellationReason( |
| PrerenderFinalStatus final_status, |
| DetailedReasonVariant explanation) |
| : final_status_(final_status), explanation_(std::move(explanation)) {} |
| |
| void PrerenderCancellationReason::ReportMetrics( |
| const std::string& histogram_suffix) const { |
| switch (final_status_) { |
| case PrerenderFinalStatus::kInactivePageRestriction: |
| CHECK(std::holds_alternative<uint64_t>(explanation_)); |
| base::UmaHistogramSparse( |
| "Prerender.CanceledForInactivePageRestriction." |
| "DisallowActivationReason" + |
| histogram_suffix, |
| std::get<uint64_t>(explanation_)); |
| break; |
| case PrerenderFinalStatus::kMojoBinderPolicy: |
| CHECK(std::holds_alternative<std::string>(explanation_)); |
| RecordPrerenderCancelledInterface(std::get<std::string>(explanation_), |
| histogram_suffix); |
| break; |
| case PrerenderFinalStatus::kDidFailLoad: |
| CHECK(std::holds_alternative<int32_t>(explanation_)); |
| RecordDidFailLoadErrorType(std::get<int32_t>(explanation_), |
| histogram_suffix); |
| break; |
| case PrerenderFinalStatus::kActivationNavigationParameterMismatch: |
| CHECK(std::holds_alternative<std::vector<PrerenderMismatchedHeaders>>( |
| explanation_) || |
| std::holds_alternative<std::monostate>(explanation_)); |
| if (auto* mismatched_headers = |
| std::get_if<std::vector<PrerenderMismatchedHeaders>>( |
| &explanation_)) { |
| ReportAllPrerenderMismatchedHeaders(*mismatched_headers, |
| histogram_suffix); |
| } |
| break; |
| default: |
| CHECK(std::holds_alternative<std::monostate>(explanation_)); |
| // Other types need not to report. |
| break; |
| } |
| } |
| |
| std::optional<std::string> |
| PrerenderCancellationReason::DisallowedMojoInterface() const { |
| switch (final_status_) { |
| case PrerenderFinalStatus::kMojoBinderPolicy: |
| return std::get<std::string>(explanation_); |
| default: |
| return std::nullopt; |
| } |
| } |
| |
| PrerenderMismatchedHeaders::PrerenderMismatchedHeaders( |
| const std::string& header_name, |
| std::optional<std::string> initial_value, |
| std::optional<std::string> activation_value) |
| : header_name(header_name), |
| initial_value(std::move(initial_value)), |
| activation_value(std::move(activation_value)) {} |
| |
| PrerenderMismatchedHeaders::~PrerenderMismatchedHeaders() = default; |
| |
| PrerenderMismatchedHeaders::PrerenderMismatchedHeaders( |
| const PrerenderMismatchedHeaders& other) = default; |
| |
| PrerenderMismatchedHeaders::PrerenderMismatchedHeaders( |
| PrerenderMismatchedHeaders&& other) = default; |
| |
| PrerenderMismatchedHeaders& PrerenderMismatchedHeaders::operator=( |
| const PrerenderMismatchedHeaders& other) = default; |
| |
| PrerenderMismatchedHeaders& PrerenderMismatchedHeaders::operator=( |
| PrerenderMismatchedHeaders&& other) = default; |
| |
| std::string GeneratePrerenderHistogramSuffix( |
| PreloadingTriggerType trigger_type, |
| const std::string& embedder_suffix) { |
| CHECK(embedder_suffix.empty() || |
| trigger_type == PreloadingTriggerType::kEmbedder); |
| switch (trigger_type) { |
| case PreloadingTriggerType::kSpeculationRule: |
| return ".SpeculationRule"; |
| case PreloadingTriggerType::kSpeculationRuleFromIsolatedWorld: |
| return ".SpeculationRuleFromIsolatedWorld"; |
| case PreloadingTriggerType::kSpeculationRuleFromAutoSpeculationRules: |
| return ".SpeculationRuleFromAutoSpeculationRules"; |
| case PreloadingTriggerType::kEmbedder: |
| return ".Embedder_" + embedder_suffix; |
| } |
| NOTREACHED(); |
| } |
| |
| void RecordPrerenderTriggered(ukm::SourceId ukm_id) { |
| ukm::builders::PrerenderPageLoad(ukm_id).SetTriggeredPrerender(true).Record( |
| ukm::UkmRecorder::Get()); |
| } |
| |
| void RecordPrerenderActivationTime( |
| base::TimeDelta delta, |
| PreloadingTriggerType trigger_type, |
| const std::string& embedder_histogram_suffix) { |
| base::UmaHistogramTimes( |
| GenerateHistogramName("Navigation.TimeToActivatePrerender", trigger_type, |
| embedder_histogram_suffix), |
| delta); |
| } |
| |
| void RecordFailedPrerenderFinalStatus( |
| const PrerenderCancellationReason& cancellation_reason, |
| const PrerenderAttributes& attributes) { |
| CHECK_NE(cancellation_reason.final_status(), |
| PrerenderFinalStatus::kActivated); |
| RecordPrerenderFinalStatusUma(cancellation_reason.final_status(), |
| attributes.trigger_type, |
| attributes.embedder_histogram_suffix); |
| |
| if (cancellation_reason.final_status() == |
| PrerenderFinalStatus::kPrerenderFailedDuringPrefetch) { |
| const std::optional<PrefetchStatus>& prefetch_status = |
| attributes.preload_pipeline_info->prefetch_status(); |
| if (prefetch_status.has_value()) { |
| base::UmaHistogramEnumeration( |
| GenerateHistogramName("Prerender.Experimental." |
| "PrefetchAheadOfPrerenderFailed.PrefetchStatus", |
| attributes.trigger_type, |
| attributes.embedder_histogram_suffix), |
| prefetch_status.value()); |
| } |
| } |
| |
| if (attributes.initiator_ukm_id != ukm::kInvalidSourceId) { |
| // `initiator_ukm_id` must be valid for the speculation rules. |
| CHECK(IsSpeculationRuleType(attributes.trigger_type)); |
| ukm::builders::PrerenderPageLoad(attributes.initiator_ukm_id) |
| .SetFinalStatus(static_cast<int>(cancellation_reason.final_status())) |
| .Record(ukm::UkmRecorder::Get()); |
| } |
| } |
| |
| void ReportSuccessActivation(const PrerenderAttributes& attributes, |
| ukm::SourceId prerendered_ukm_id) { |
| RecordPrerenderFinalStatusUma(PrerenderFinalStatus::kActivated, |
| attributes.trigger_type, |
| attributes.embedder_histogram_suffix); |
| if (attributes.initiator_ukm_id != ukm::kInvalidSourceId) { |
| // `initiator_ukm_id` must be valid only for the speculation rules. |
| CHECK(IsSpeculationRuleType(attributes.trigger_type)); |
| ukm::builders::PrerenderPageLoad(attributes.initiator_ukm_id) |
| .SetFinalStatus(static_cast<int>(PrerenderFinalStatus::kActivated)) |
| .Record(ukm::UkmRecorder::Get()); |
| } |
| |
| if (prerendered_ukm_id != ukm::kInvalidSourceId) { |
| ukm::builders::PrerenderPageLoad(prerendered_ukm_id) |
| .SetFinalStatus(static_cast<int>(PrerenderFinalStatus::kActivated)) |
| .Record(ukm::UkmRecorder::Get()); |
| } |
| } |
| |
| void RecordPrerenderActivationNavigationParamsMatch( |
| PrerenderHost::ActivationNavigationParamsMatch result, |
| const std::string& histogram_suffix) { |
| base::UmaHistogramEnumeration( |
| "Prerender.Experimental.ActivationNavigationParamsMatch" + |
| histogram_suffix, |
| result); |
| } |
| |
| void RecordPrerenderRedirectionMismatchType( |
| PrerenderCrossOriginRedirectionMismatch mismatch_type, |
| const std::string& histogram_suffix) { |
| base::UmaHistogramEnumeration( |
| "Prerender.Experimental.PrerenderCrossOriginRedirectionMismatch" + |
| histogram_suffix, |
| mismatch_type); |
| } |
| |
| void RecordPrerenderRedirectionProtocolChange( |
| PrerenderCrossOriginRedirectionProtocolChange change_type, |
| const std::string& histogram_suffix) { |
| base::UmaHistogramEnumeration( |
| "Prerender.Experimental.CrossOriginRedirectionProtocolChange" + |
| histogram_suffix, |
| change_type); |
| } |
| |
| void RecordPrerenderActivationTransition( |
| int32_t potential_activation_transition, |
| const std::string& histogram_suffix) { |
| base::UmaHistogramSparse( |
| "Prerender.Experimental.ActivationTransitionMismatch" + histogram_suffix, |
| potential_activation_transition); |
| } |
| |
| static_assert( |
| static_cast<int>(PrerenderBackNavigationEligibility::kMaxValue) + |
| static_cast<int>( |
| PreloadingEligibility::kPreloadingEligibilityContentStart2) < |
| static_cast<int>(PreloadingEligibility::kPreloadingEligibilityContentEnd2)); |
| |
| PreloadingEligibility ToPreloadingEligibility( |
| PrerenderBackNavigationEligibility eligibility) { |
| if (eligibility == PrerenderBackNavigationEligibility::kEligible) { |
| return PreloadingEligibility::kEligible; |
| } |
| |
| return static_cast<PreloadingEligibility>( |
| static_cast<int>(eligibility) + |
| static_cast<int>( |
| PreloadingEligibility::kPreloadingEligibilityContentStart2)); |
| } |
| |
| void RecordPrerenderBackNavigationEligibility( |
| PreloadingPredictor predictor, |
| PrerenderBackNavigationEligibility eligibility, |
| PreloadingAttempt* preloading_attempt) { |
| const std::string histogram_name = |
| std::string("Preloading.PrerenderBackNavigationEligibility.") + |
| std::string(predictor.name()); |
| base::UmaHistogramEnumeration(histogram_name, eligibility); |
| |
| if (preloading_attempt) { |
| preloading_attempt->SetEligibility(ToPreloadingEligibility(eligibility)); |
| } |
| } |
| |
| void RecordPrerenderActivationCommitDeferTime( |
| base::TimeDelta time_delta, |
| PreloadingTriggerType trigger_type, |
| const std::string& embedder_histogram_suffix) { |
| base::UmaHistogramTimes( |
| GenerateHistogramName("Navigation.Prerender.ActivationCommitDeferTime", |
| trigger_type, embedder_histogram_suffix), |
| time_delta); |
| } |
| |
| void RecordReceivedPrerendersPerPrimaryPageChangedCount( |
| int number, |
| PreloadingTriggerType trigger_type, |
| const std::string& eagerness_category) { |
| base::UmaHistogramCounts100( |
| GenerateHistogramName("Prerender.Experimental." |
| "ReceivedPrerendersPerPrimaryPageChangedCount2", |
| trigger_type, /*embedder_suffix=*/"") + |
| "." + eagerness_category, |
| number); |
| } |
| |
| } // namespace content |