blob: 3bf2c304e9d93eedceded9c92da17b22b2b76584 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/metrics/chrome_browser_sampling_trials.h"
#include "base/feature_list.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/field_trial_params.h"
#include "base/strings/string_number_conversions.h"
#include "chrome/browser/metrics/chrome_metrics_services_manager_client.h"
#include "chrome/common/channel_info.h"
#include "components/ukm/ukm_recorder_impl.h"
#include "components/version_info/channel.h"
namespace metrics {
namespace {
// Note that the trial name must be kept in sync with the server config
// controlling sampling. If they don't match, then clients will be shuffled into
// different groups when the server config takes over from the fallback trial.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_ANDROID)
constexpr char kSamplingTrialName[] = "MetricsAndCrashSampling";
#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_ANDROID)
#if BUILDFLAG(IS_ANDROID)
constexpr char kPostFREFixSamplingTrialName[] =
"PostFREFixMetricsAndCrashSampling";
#endif // BUILDFLAG(IS_ANDROID)
constexpr char kUkmSamplingTrialName[] = "UkmSamplingRate";
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_ANDROID)
// Appends a group to the sampling controlling |trial|. The group will be
// associated with a variation param for reporting sampling |rate| in per mille.
void AppendSamplingTrialGroup(const std::string& group_name,
int rate,
bool disable_crashes,
base::FieldTrial* trial) {
std::map<std::string, std::string> params = {
{metrics::internal::kRateParamName, base::NumberToString(rate)}};
if (disable_crashes) {
params.insert({"disable_crashes", "true"});
}
base::AssociateFieldTrialParams(trial->trial_name(), group_name, params);
trial->AppendGroup(group_name, rate);
}
// Unconditionally attempts to create a field trial to control client side
// metrics/crash sampling to use as a fallback when one hasn't been
// provided. This is expected to occur on first-run on platforms that don't
// have first-run variations support, or when no valid seed is available. This
// should only be called when there is no existing field trial controlling the
// sampling feature. |feature_name| is the name of the feature that determines
// sampling.
// Rates:
// |sampled_in_rate_per_mille| is the sampling rate per mille.
// |reporting_full_rate_per_mille| is the rate for clients who are sampled in,
// in the special ReportingFull group.
// All other clients are in the OutOfSampling group.
void CreateFallbackSamplingTrial(
const base::FieldTrial::EntropyProvider& entropy_provider,
const std::string& trial_name,
const std::string& feature_name,
const int sampled_in_rate_per_mille,
const int reporting_full_rate_per_mille,
const bool starts_active,
base::FeatureList* feature_list) {
scoped_refptr<base::FieldTrial> trial(
base::FieldTrialList::FactoryGetFieldTrial(
trial_name, /*total_probability=*/1000, "Default", entropy_provider));
// Like the trial name, the order that these groups are added to the trial
// must be kept in sync with the order that they appear in the server config.
// The desired order is: OutOfReportingSample, ReportingFull,
// InReportingSample.
const char kSampledOutGroup[] = "OutOfReportingSample";
const int sampled_out_rate_per_mille =
1000 - sampled_in_rate_per_mille - reporting_full_rate_per_mille;
AppendSamplingTrialGroup(kSampledOutGroup, sampled_out_rate_per_mille,
/*disable_crashes=*/false, trial.get());
// This group uploads to UMA but does not upload crashes.
const char kReportingFullGroup[] = "ReportingFull";
AppendSamplingTrialGroup(kReportingFullGroup, reporting_full_rate_per_mille,
/*disable_crashes=*/true, trial.get());
const char kInSampleGroup[] = "InReportingSample";
AppendSamplingTrialGroup(kInSampleGroup, sampled_in_rate_per_mille,
/*disable_crashes=*/false, trial.get());
// Set up the feature. This must be done after all groups are added since
// GetGroupNameWithoutActivation() will finalize the group choice.
const std::string& group_name = trial->GetGroupNameWithoutActivation();
// Note that this will set both ReportingFull and InReportingSample to enable
// the feature.
feature_list->RegisterFieldTrialOverride(
feature_name,
group_name == kSampledOutGroup
? base::FeatureList::OVERRIDE_DISABLE_FEATURE
: base::FeatureList::OVERRIDE_ENABLE_FEATURE,
trial.get());
if (starts_active) {
trial->Activate();
}
}
#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_ANDROID)
// Unconditionally attempts to create a field trial to control client side
// UKM sampling to use as a fallback when one hasn't been provided. This is
// expected to occur on first-run on platforms that don't have first-run
// variations support. This should only be called when there is no existing
// field trial controlling the sampling feature.
void CreateFallbackUkmSamplingTrial(
const base::FieldTrial::EntropyProvider& entropy_provider,
bool is_stable_channel,
base::FeatureList* feature_list) {
static const char kSampledGroup_Stable[] = "Sampled_NoSeed_Stable";
static const char kSampledGroup_Other[] = "Sampled_NoSeed_Other";
const char* sampled_group = kSampledGroup_Other;
int default_sampling = 1; // Sampling is 1-in-N; this is N.
// Nothing is sampled out except for "stable" which omits almost everything
// in this configuration. This is done so that clients that fail to receive
// a configuration from the server do not bias aggregated results because
// of a relatively large number of records from them.
if (is_stable_channel) {
sampled_group = kSampledGroup_Stable;
default_sampling = 1000000;
}
scoped_refptr<base::FieldTrial> trial(
base::FieldTrialList::FactoryGetFieldTrial(
kUkmSamplingTrialName, /*total_probability=*/100, sampled_group,
entropy_provider));
// Everybody (100%) should have a sampling configuration.
std::map<std::string, std::string> params = {
{"_default_sampling", base::NumberToString(default_sampling)}};
base::AssociateFieldTrialParams(trial->trial_name(), sampled_group, params);
trial->AppendGroup(sampled_group, 100);
// Setup the feature.
feature_list->RegisterFieldTrialOverride(
ukm::kUkmSamplingRateFeature.name,
base::FeatureList::OVERRIDE_ENABLE_FEATURE, trial.get());
}
} // namespace
void CreateFallbackSamplingTrialsIfNeeded(
const base::FieldTrial::EntropyProvider& entropy_provider,
base::FeatureList* feature_list) {
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_ANDROID)
const bool is_stable = chrome::GetChannel() == version_info::Channel::STABLE;
if (!base::FieldTrialList::TrialExists(kSamplingTrialName)) {
#if BUILDFLAG(IS_WIN)
// On all channels except stable, we sample out at a minimal rate to ensure
// the code paths are exercised in the wild before hitting stable.
const int kPreStableSampledInRatePerMille = 990; // 99%
const int kPreStableReportingFullRatePerMille = 5; // 0.5%
// This leaves 0.5% for OutOfReportingSample.
const int kStableSampledInRatePerMille = 100; // 10%
const int kStableReportingFullRatePerMille = 900; // 90%
#endif // BUILDFLAG(IS_WIN)
#if BUILDFLAG(IS_ANDROID)
// On all channels except stable, we sample out at a minimal rate to ensure
// the code paths are exercised in the wild before hitting stable.
const int kPreStableSampledInRatePerMille = 995; // 99.5%
const int kPreStableReportingFullRatePerMille = 0; // 0%
// This leaves 0.5% for OutOfReportingSample.
// We use 5.3% for this set of users to work around an old bug
// (crbug.com/1306481). This should be ~10% in practice.
const int kStableSampledInRatePerMille = 53; // 5.3%
const int kStableReportingFullRatePerMille = 0; // 0%
#endif // BUILDFLAG(IS_ANDROID)
const int kSamplingInRatePerMille = is_stable
? kStableSampledInRatePerMille
: kPreStableSampledInRatePerMille;
const int kReportingFullRatePerMille =
is_stable ? kStableReportingFullRatePerMille
: kPreStableReportingFullRatePerMille;
// Note that the trial has to be activated immediately. Otherwise, it would
// be possible for this session to crash before its feature was queried, and
// the independent log produced would not contain the sampling trial.
CreateFallbackSamplingTrial(
entropy_provider, kSamplingTrialName,
metrics::internal::kMetricsReportingFeature.name,
kSamplingInRatePerMille, kReportingFullRatePerMille,
/*starts_active=*/true, feature_list);
}
#if BUILDFLAG(IS_ANDROID)
if (!base::FieldTrialList::TrialExists(kPostFREFixSamplingTrialName)) {
// On all channels except stable, we sample out at a minimal rate to ensure
// the code paths are exercised in the wild before hitting stable.
const int kPreStableSampledInRatePerMille = 995; // 99.5%
// This is meant to be 10%, and this population, unlike the set of users
// under the kSamplingTrialName trial should correctly be 10% in practice.
const int kStableSampledInRatePerMille = 100; // 10%
const int kReportingFullRatePerMille = 0;
// Note that as per the serverside config, this trial does not start active
// (so that it is possible to determine from the serverside whether the
// client used the old or new trial to determine sampling). So if Chrome
// crashes before its feature is queried, the independent log produced will
// not contain this trial, even if the client normally uses this trial to
// determine sampling.
CreateFallbackSamplingTrial(
entropy_provider, kPostFREFixSamplingTrialName,
metrics::internal::kPostFREFixMetricsReportingFeature.name,
is_stable ? kStableSampledInRatePerMille
: kPreStableSampledInRatePerMille,
kReportingFullRatePerMille,
/*starts_active=*/false, feature_list);
}
#endif // BUILDFLAG(IS_ANDROID)
#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_ANDROID)
}
void CreateFallbackUkmSamplingTrialIfNeeded(
const base::FieldTrial::EntropyProvider& entropy_provider,
base::FeatureList* feature_list) {
if (!base::FieldTrialList::TrialExists(kUkmSamplingTrialName)) {
const bool is_stable =
chrome::GetChannel() == version_info::Channel::STABLE;
CreateFallbackUkmSamplingTrial(entropy_provider, is_stable, feature_list);
}
}
} // namespace metrics