#include "chrome/common/profiler/thread_profiler_configuration.h"
#include <variant>
#include "base/check.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/numerics/ranges.h"
#include "base/profiler/stack_sampler.h"
#include "base/rand_util.h"
#include "build/branding_buildflags.h"
#include "chrome/common/channel_info.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/profiler/core_unwinders.h"
#include "chrome/common/profiler/process_type.h"
#include "chrome/common/profiler/thread_profiler_platform_configuration.h"
#include "components/sampling_profiler/process_type.h"
#include "components/version_info/version_info.h"
namespace {
bool IsBrowserTestModeEnabled() {
const base::CommandLine* command_line =
base::CommandLine::ForCurrentProcess();
return command_line->GetSwitchValueASCII(switches::kStartStackProfiler) ==
switches::kStartStackProfilerBrowserTest;
}
std::optional<version_info::Channel> GetReleaseChannel() {
#if defined(OFFICIAL_BUILD) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
return chrome::GetChannel();
#else
return std::nullopt;
#endif
}
}
ThreadProfilerConfiguration* ThreadProfilerConfiguration::Get() {
static base::NoDestructor<ThreadProfilerConfiguration>
thread_profiler_configuration;
return thread_profiler_configuration.get();
}
base::StackSamplingProfiler::SamplingParams
ThreadProfilerConfiguration::GetSamplingParams() const {
base::StackSamplingProfiler::SamplingParams params;
params.initial_delay = base::Milliseconds(0);
const base::TimeDelta duration =
base::Seconds(IsBrowserTestModeEnabled() ? 1 : 30);
params.sampling_interval = base::Milliseconds(100);
params.samples_per_profile = duration / params.sampling_interval;
return params;
}
bool ThreadProfilerConfiguration::IsProfilerEnabledForCurrentProcess() const {
if (const ChildProcessConfiguration* child_process_configuration =
std::get_if<ChildProcessConfiguration>(&configuration_)) {
return *child_process_configuration == kChildProcessProfileEnabled;
}
const auto& config = std::get<BrowserProcessConfiguration>(configuration_);
return EnableForVariationGroup(config.variation_group) &&
IsProcessGloballyEnabled(
config,
GetProfilerProcessType(*base::CommandLine::ForCurrentProcess()));
}
bool ThreadProfilerConfiguration::IsProfilerEnabledForCurrentProcessAndThread(
sampling_profiler::ProfilerThreadType thread) const {
return IsProfilerEnabledForCurrentProcess() &&
platform_configuration_->IsEnabledForThread(
GetProfilerProcessType(*base::CommandLine::ForCurrentProcess()),
thread, GetReleaseChannel());
}
bool ThreadProfilerConfiguration::GetSyntheticFieldTrial(
std::string* trial_name,
std::string* group_name) const {
DCHECK(std::holds_alternative<BrowserProcessConfiguration>(configuration_));
const auto& config = std::get<BrowserProcessConfiguration>(configuration_);
if (!config.variation_group.has_value()) {
return false;
}
*trial_name = "SyntheticStackProfilingConfiguration";
*group_name = std::string();
switch (*config.variation_group) {
case kProfileDisabled:
*group_name = "Disabled";
break;
case kProfileDisabledModuleNotInstalled:
*group_name = "DisabledModuleNotInstalled";
break;
case kProfileControl:
*group_name = "Control";
break;
case kProfileEnabled:
*group_name = "Enabled";
break;
case kProfileDisabledOutsideOfExperiment:
*group_name = "DisabledOutsideOfExperiment";
break;
}
return true;
}
bool ThreadProfilerConfiguration::IsProfilerEnabledForChildProcess(
sampling_profiler::ProfilerProcessType child_process) const {
const auto& config = std::get<BrowserProcessConfiguration>(configuration_);
const double enable_fraction =
platform_configuration_->GetChildProcessPerExecutionEnableFraction(
child_process);
const bool in_enabled_fraction = base::RandDouble() < enable_fraction;
return EnableForVariationGroup(config.variation_group) &&
IsProcessGloballyEnabled(config, child_process) && in_enabled_fraction;
}
void ThreadProfilerConfiguration::AppendCommandLineSwitchForChildProcess(
base::CommandLine* child_process_command_line) const {
DCHECK(std::holds_alternative<BrowserProcessConfiguration>(configuration_));
if (!IsProfilerEnabledForChildProcess(
GetProfilerProcessType(*child_process_command_line))) {
return;
}
if (IsBrowserTestModeEnabled()) {
child_process_command_line->AppendSwitchASCII(
switches::kStartStackProfiler,
switches::kStartStackProfilerBrowserTest);
} else {
child_process_command_line->AppendSwitch(switches::kStartStackProfiler);
}
}
ThreadProfilerConfiguration::ThreadProfilerConfiguration()
: platform_configuration_(ThreadProfilerPlatformConfiguration::Create(
IsBrowserTestModeEnabled())),
configuration_(GenerateConfiguration(
GetProfilerProcessType(*base::CommandLine::ForCurrentProcess()),
*platform_configuration_)) {
}
bool ThreadProfilerConfiguration::EnableForVariationGroup(
std::optional<VariationGroup> variation_group) {
return variation_group.has_value() &&
(*variation_group == kProfileEnabled ||
*variation_group == kProfileControl);
}
bool ThreadProfilerConfiguration::IsProcessGloballyEnabled(
const ThreadProfilerConfiguration::BrowserProcessConfiguration& config,
sampling_profiler::ProfilerProcessType process) {
return !config.process_type_to_sample.has_value() ||
process == *config.process_type_to_sample;
}
ThreadProfilerConfiguration::VariationGroup
ThreadProfilerConfiguration::ChooseVariationGroup(
base::span<const Variation> variations,
double randValue) {
double total_weight = 0;
for (const Variation& variation : variations)
total_weight += variation.weight;
DCHECK(base::IsApproximatelyEqual(total_weight, 100.0, 0.0001));
double chosen = randValue * total_weight;
double cumulative_weight = 0;
for (const Variation& variation : variations) {
cumulative_weight += variation.weight;
if (chosen < cumulative_weight) {
return variation.group;
}
}
return variations.back().group;
}
ThreadProfilerConfiguration::BrowserProcessConfiguration
ThreadProfilerConfiguration::GenerateBrowserProcessConfiguration(
const ThreadProfilerPlatformConfiguration& platform_configuration) {
const base::CommandLine* command_line =
base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(switches::kDisableStackProfiler))
return {std::nullopt, std::nullopt};
const std::optional<version_info::Channel> release_channel =
GetReleaseChannel();
if (!platform_configuration.IsSupported(release_channel))
return {std::nullopt, std::nullopt};
if (!AreUnwindPrerequisitesAvailable(
release_channel.value_or(version_info::Channel::UNKNOWN))) {
return {kProfileDisabledModuleNotInstalled, std::nullopt};
}
ThreadProfilerPlatformConfiguration::RelativePopulations
relative_populations =
platform_configuration.GetEnableRates(release_channel);
const std::optional<sampling_profiler::ProfilerProcessType>
process_type_to_sample = platform_configuration.ChooseEnabledProcess();
return {ChooseVariationGroup(
{
{kProfileDisabledOutsideOfExperiment,
relative_populations.disabled},
{kProfileEnabled, relative_populations.enabled},
{kProfileControl, relative_populations.experiment / 2.0},
{kProfileDisabled, relative_populations.experiment / 2.0},
},
base::RandDouble()),
process_type_to_sample};
}
ThreadProfilerConfiguration::ChildProcessConfiguration
ThreadProfilerConfiguration::GenerateChildProcessConfiguration(
const base::CommandLine& command_line) {
return command_line.HasSwitch(switches::kStartStackProfiler)
? kChildProcessProfileEnabled
: kChildProcessProfileDisabled;
}
ThreadProfilerConfiguration::Configuration
ThreadProfilerConfiguration::GenerateConfiguration(
sampling_profiler::ProfilerProcessType process,
const ThreadProfilerPlatformConfiguration& platform_configuration) {
if (process == sampling_profiler::ProfilerProcessType::kBrowser) {
return GenerateBrowserProcessConfiguration(platform_configuration);
}
return GenerateChildProcessConfiguration(
*base::CommandLine::ForCurrentProcess());
}