|  | // 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_data.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---29} bits are the encoded hash number. | 
|  | // {30, 31} 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 |