blob: 428fc41cec84ffae8333f36ad019348e26a83433 [file] [log] [blame]
// Copyright 2019 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/optimization_guide/optimization_guide_navigation_data.h"
#include "base/base64.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/optimization_guide/optimization_guide_web_contents_observer.h"
#include "components/optimization_guide/hints_processing_util.h"
#include "content/public/browser/navigation_handle.h"
#include "net/nqe/effective_connection_type.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "services/metrics/public/cpp/ukm_source.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
namespace {
// The returned string is used to record histograms for the optimization target.
// Also add the string to OptimizationGuide.OptimizationTargets histogram
// suffixes in histograms.xml.
std::string GetStringNameForOptimizationTarget(
optimization_guide::proto::OptimizationTarget optimization_target) {
switch (optimization_target) {
case optimization_guide::proto::OPTIMIZATION_TARGET_UNKNOWN:
return "Unknown";
case optimization_guide::proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD:
return "PainfulPageLoad";
}
NOTREACHED();
return std::string();
}
} // namespace
OptimizationGuideNavigationData::OptimizationGuideNavigationData(
int64_t navigation_id)
: navigation_id_(navigation_id) {}
OptimizationGuideNavigationData::~OptimizationGuideNavigationData() {
RecordMetrics();
}
// static
OptimizationGuideNavigationData*
OptimizationGuideNavigationData::GetFromNavigationHandle(
content::NavigationHandle* navigation_handle) {
OptimizationGuideWebContentsObserver*
optimization_guide_web_contents_observer =
OptimizationGuideWebContentsObserver::FromWebContents(
navigation_handle->GetWebContents());
if (!optimization_guide_web_contents_observer)
return nullptr;
return optimization_guide_web_contents_observer
->GetOrCreateOptimizationGuideNavigationData(navigation_handle);
}
void OptimizationGuideNavigationData::RecordMetrics() const {
RecordOptimizationTypeAndTargetDecisions();
RecordOptimizationGuideUKM();
}
void OptimizationGuideNavigationData::RecordOptimizationTypeAndTargetDecisions()
const {
// Record optimization type decisions.
for (const auto& optimization_type_decision : optimization_type_decisions_) {
optimization_guide::proto::OptimizationType optimization_type =
optimization_type_decision.first;
optimization_guide::OptimizationTypeDecision decision =
optimization_type_decision.second;
base::UmaHistogramExactLinear(
base::StringPrintf("OptimizationGuide.ApplyDecision.%s",
optimization_guide::GetStringNameForOptimizationType(
optimization_type)
.c_str()),
static_cast<int>(decision),
static_cast<int>(
optimization_guide::OptimizationTypeDecision::kMaxValue));
}
// Record optimization target decisions.
for (const auto& optimization_target_decision :
optimization_target_decisions_) {
optimization_guide::proto::OptimizationTarget optimization_target =
optimization_target_decision.first;
optimization_guide::OptimizationTargetDecision decision =
optimization_target_decision.second;
base::UmaHistogramExactLinear(
base::StringPrintf(
"OptimizationGuide.TargetDecision.%s",
GetStringNameForOptimizationTarget(optimization_target).c_str()),
static_cast<int>(decision),
static_cast<int>(
optimization_guide::OptimizationTargetDecision::kMaxValue));
}
}
void OptimizationGuideNavigationData::RecordOptimizationGuideUKM() const {
bool did_record_metric = false;
ukm::SourceId ukm_source_id =
ukm::ConvertToSourceId(navigation_id_, ukm::SourceIdType::NAVIGATION_ID);
ukm::builders::OptimizationGuide builder(ukm_source_id);
// Record model metrics.
for (const auto& optimization_target_model_version :
optimization_target_model_versions_) {
if (optimization_target_model_version.first ==
optimization_guide::proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD) {
did_record_metric = true;
builder.SetPainfulPageLoadModelVersion(
optimization_target_model_version.second);
}
}
for (const auto& optimization_target_model_prediction_score :
optimization_target_model_prediction_scores_) {
if (optimization_target_model_prediction_score.first ==
optimization_guide::proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD) {
did_record_metric = true;
builder.SetPainfulPageLoadModelPredictionScore(static_cast<int64_t>(
100 * optimization_target_model_prediction_score.second));
}
}
for (const auto& model_feature : prediction_model_features_) {
switch (model_feature.first) {
case optimization_guide::proto::CLIENT_MODEL_FEATURE_UNKNOWN: {
continue;
}
case optimization_guide::proto::
CLIENT_MODEL_FEATURE_EFFECTIVE_CONNECTION_TYPE: {
builder.SetPredictionModelFeatureEffectiveConnectionType(
static_cast<net::EffectiveConnectionType>(model_feature.second));
did_record_metric = true;
continue;
}
case optimization_guide::proto::CLIENT_MODEL_FEATURE_PAGE_TRANSITION: {
builder.SetPredictionModelFeaturePageTransition(
static_cast<ui::PageTransition>(model_feature.second));
did_record_metric = true;
continue;
}
case optimization_guide::proto::
CLIENT_MODEL_FEATURE_SITE_ENGAGEMENT_SCORE: {
builder.SetPredictionModelFeatureSiteEngagementScore(
static_cast<int>(std::roundf(model_feature.second / 10.0) * 10));
did_record_metric = true;
continue;
}
case optimization_guide::proto::
CLIENT_MODEL_FEATURE_SAME_ORIGIN_NAVIGATION: {
builder.SetPredictionModelFeatureIsSameOriginNavigation(
static_cast<int>(model_feature.second));
did_record_metric = true;
continue;
}
case optimization_guide::proto::
CLIENT_MODEL_FEATURE_FIRST_CONTENTFUL_PAINT_SESSION_MEAN: {
builder.SetPredictionModelFeatureNavigationToFCPSessionMean(
static_cast<int>(model_feature.second));
did_record_metric = true;
continue;
}
case optimization_guide::proto::
CLIENT_MODEL_FEATURE_FIRST_CONTENTFUL_PAINT_SESSION_STANDARD_DEVIATION: {
builder.SetPredictionModelFeatureNavigationToFCPSessionStdDev(
static_cast<int>(model_feature.second));
did_record_metric = true;
continue;
}
case optimization_guide::proto::
CLIENT_MODEL_FEATURE_FIRST_CONTENTFUL_PAINT_PREVIOUS_PAGE_LOAD: {
builder.SetPredictionModelFeaturePreviousPageLoadNavigationToFCP(
static_cast<int>(model_feature.second));
did_record_metric = true;
continue;
}
}
}
// Record hints fetch metrics.
if (hints_fetch_start_.has_value()) {
if (hints_fetch_latency().has_value()) {
builder.SetNavigationHintsFetchRequestLatency(
hints_fetch_latency()->InMilliseconds());
} else {
builder.SetNavigationHintsFetchRequestLatency(INT64_MAX);
}
did_record_metric = true;
}
if (hints_fetch_attempt_status_.has_value()) {
builder.SetNavigationHintsFetchAttemptStatus(
static_cast<int>(*hints_fetch_attempt_status_));
did_record_metric = true;
}
// Record registered types/targets metrics.
if (!registered_optimization_types_.empty()) {
int64_t types_bitmask = 0;
for (const auto& optimization_type : registered_optimization_types_) {
types_bitmask |= (1 << static_cast<int>(optimization_type));
}
builder.SetRegisteredOptimizationTypes(types_bitmask);
did_record_metric = true;
}
if (!registered_optimization_targets_.empty()) {
int64_t targets_bitmask = 0;
for (const auto& optimization_target : registered_optimization_targets_) {
targets_bitmask |= (1 << static_cast<int>(optimization_target));
}
builder.SetRegisteredOptimizationTargets(targets_bitmask);
did_record_metric = true;
}
// Only record UKM if a metric was recorded.
if (did_record_metric)
builder.Record(ukm::UkmRecorder::Get());
}
base::Optional<optimization_guide::OptimizationTypeDecision>
OptimizationGuideNavigationData::GetDecisionForOptimizationType(
optimization_guide::proto::OptimizationType optimization_type) const {
auto optimization_type_decision_iter =
optimization_type_decisions_.find(optimization_type);
if (optimization_type_decision_iter == optimization_type_decisions_.end())
return base::nullopt;
return optimization_type_decision_iter->second;
}
void OptimizationGuideNavigationData::SetDecisionForOptimizationType(
optimization_guide::proto::OptimizationType optimization_type,
optimization_guide::OptimizationTypeDecision decision) {
optimization_type_decisions_[optimization_type] = decision;
}
base::Optional<optimization_guide::OptimizationTargetDecision>
OptimizationGuideNavigationData::GetDecisionForOptimizationTarget(
optimization_guide::proto::OptimizationTarget optimization_target) const {
auto optimization_target_decision_iter =
optimization_target_decisions_.find(optimization_target);
if (optimization_target_decision_iter == optimization_target_decisions_.end())
return base::nullopt;
return optimization_target_decision_iter->second;
}
void OptimizationGuideNavigationData::SetDecisionForOptimizationTarget(
optimization_guide::proto::OptimizationTarget optimization_target,
optimization_guide::OptimizationTargetDecision decision) {
optimization_target_decisions_[optimization_target] = decision;
}
base::Optional<int64_t>
OptimizationGuideNavigationData::GetModelVersionForOptimizationTarget(
optimization_guide::proto::OptimizationTarget optimization_target) const {
auto optimization_target_model_version_iter =
optimization_target_model_versions_.find(optimization_target);
if (optimization_target_model_version_iter ==
optimization_target_model_versions_.end())
return base::nullopt;
return optimization_target_model_version_iter->second;
}
void OptimizationGuideNavigationData::SetModelVersionForOptimizationTarget(
optimization_guide::proto::OptimizationTarget optimization_target,
int64_t model_version) {
optimization_target_model_versions_[optimization_target] = model_version;
}
base::Optional<double>
OptimizationGuideNavigationData::GetModelPredictionScoreForOptimizationTarget(
optimization_guide::proto::OptimizationTarget optimization_target) const {
auto optimization_target_model_prediction_score_iter =
optimization_target_model_prediction_scores_.find(optimization_target);
if (optimization_target_model_prediction_score_iter ==
optimization_target_model_prediction_scores_.end())
return base::nullopt;
return optimization_target_model_prediction_score_iter->second;
}
void OptimizationGuideNavigationData::
SetModelPredictionScoreForOptimizationTarget(
optimization_guide::proto::OptimizationTarget optimization_target,
double model_prediction_score) {
optimization_target_model_prediction_scores_[optimization_target] =
model_prediction_score;
}
void OptimizationGuideNavigationData::SetValueForModelFeature(
optimization_guide::proto::ClientModelFeature model_feature,
float value) {
prediction_model_features_[model_feature] = value;
}
base::Optional<float>
OptimizationGuideNavigationData::GetValueForModelFeatureForTesting(
optimization_guide::proto::ClientModelFeature model_feature) {
auto it = prediction_model_features_.find(model_feature);
if (it == prediction_model_features_.end())
return base::nullopt;
return it->second;
}
base::Optional<base::TimeDelta>
OptimizationGuideNavigationData::hints_fetch_latency() const {
if (!hints_fetch_start_ || !hints_fetch_end_) {
// Either a fetch was not initiated for this navigation or the fetch did not
// completely successfully.
return base::nullopt;
}
if (*hints_fetch_end_ < *hints_fetch_start_) {
// This can happen if a hints fetch was started for a redirect, but the
// fetch had not successfully completed yet.
return base::nullopt;
}
return *hints_fetch_end_ - *hints_fetch_start_;
}