| // Copyright 2020 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/common/profiler/thread_profiler_platform_configuration.h" |
| |
| #include "base/command_line.h" |
| #include "base/containers/flat_map.h" |
| #include "base/feature_list.h" |
| #include "base/functional/callback.h" |
| #include "base/notreached.h" |
| #include "base/profiler/stack_sampling_profiler.h" |
| #include "base/rand_util.h" |
| #include "build/build_config.h" |
| #include "chrome/common/profiler/process_type.h" |
| #include "components/sampling_profiler/process_type.h" |
| |
| BASE_FEATURE(kSamplingProfilerOnWorkerThreads, |
| base::FEATURE_DISABLED_BY_DEFAULT); |
| |
| namespace { |
| |
| // The default configuration to use in the absence of special circumstances on a |
| // specific platform. |
| class DefaultPlatformConfiguration |
| : public ThreadProfilerPlatformConfiguration { |
| public: |
| explicit DefaultPlatformConfiguration(bool browser_test_mode_enabled); |
| |
| RelativePopulations GetEnableRates( |
| std::optional<version_info::Channel> release_channel) const override; |
| |
| double GetChildProcessPerExecutionEnableFraction( |
| sampling_profiler::ProfilerProcessType process) const override; |
| |
| std::optional<sampling_profiler::ProfilerProcessType> ChooseEnabledProcess() |
| const override; |
| |
| bool IsEnabledForThread( |
| sampling_profiler::ProfilerProcessType process, |
| sampling_profiler::ProfilerThreadType thread, |
| std::optional<version_info::Channel> release_channel) const override; |
| |
| protected: |
| bool IsSupportedForChannel( |
| std::optional<version_info::Channel> release_channel) const override; |
| |
| bool browser_test_mode_enabled() const { return browser_test_mode_enabled_; } |
| |
| private: |
| const bool browser_test_mode_enabled_; |
| }; |
| |
| DefaultPlatformConfiguration::DefaultPlatformConfiguration( |
| bool browser_test_mode_enabled) |
| : browser_test_mode_enabled_(browser_test_mode_enabled) {} |
| |
| ThreadProfilerPlatformConfiguration::RelativePopulations |
| DefaultPlatformConfiguration::GetEnableRates( |
| std::optional<version_info::Channel> release_channel) const { |
| CHECK(IsSupportedForChannel(release_channel)); |
| |
| if (!release_channel) { |
| // This is a local/CQ build. |
| return RelativePopulations{0.0, 100.0, 0.0}; |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| if (browser_test_mode_enabled()) { |
| // This is a browser test or maybe a tast test that called |
| // chrome.EnableStackSampledMetrics(). |
| return RelativePopulations{0.0, 100.0, 0.0}; |
| } |
| #endif |
| |
| CHECK_NE(*release_channel, version_info::Channel::UNKNOWN); |
| |
| switch (*release_channel) { |
| case version_info::Channel::BETA: { |
| // TODO(crbug.com/1497983): Ramp up enable rate on Non-Android platforms. |
| return RelativePopulations{90.0, 0.0, 10.0}; |
| } |
| case version_info::Channel::STABLE: { |
| static constexpr double experiment_rate = 0.006; |
| return RelativePopulations{100.0 - experiment_rate, 0.0, experiment_rate}; |
| } |
| default: |
| return RelativePopulations{0.0, 80.0, 20.0}; |
| } |
| } |
| |
| double DefaultPlatformConfiguration::GetChildProcessPerExecutionEnableFraction( |
| sampling_profiler::ProfilerProcessType process) const { |
| DCHECK_NE(sampling_profiler::ProfilerProcessType::kBrowser, process); |
| |
| // Profile all supported processes in browser test mode. |
| if (browser_test_mode_enabled()) { |
| return 1.0; |
| } |
| |
| switch (process) { |
| case sampling_profiler::ProfilerProcessType::kGpu: |
| case sampling_profiler::ProfilerProcessType::kNetworkService: |
| return 1.0; |
| |
| case sampling_profiler::ProfilerProcessType::kRenderer: |
| // Run the profiler in 20% of the processes to collect roughly as many |
| // profiles for renderer processes as browser processes. |
| return 0.2; |
| |
| default: |
| return 0.0; |
| } |
| } |
| |
| std::optional<sampling_profiler::ProfilerProcessType> |
| DefaultPlatformConfiguration::ChooseEnabledProcess() const { |
| // Ignore the setting, sampling more than one process. |
| return std::nullopt; |
| } |
| |
| bool DefaultPlatformConfiguration::IsEnabledForThread( |
| sampling_profiler::ProfilerProcessType process, |
| sampling_profiler::ProfilerThreadType thread, |
| std::optional<version_info::Channel> release_channel) const { |
| if (thread == sampling_profiler::ProfilerThreadType::kThreadPoolWorker) { |
| return base::FeatureList::IsEnabled(kSamplingProfilerOnWorkerThreads); |
| } |
| // Enable for all supported threads. |
| return true; |
| } |
| |
| bool DefaultPlatformConfiguration::IsSupportedForChannel( |
| std::optional<version_info::Channel> release_channel) const { |
| // The profiler is always supported for local builds and the CQ. |
| if (!release_channel) |
| return true; |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| if (browser_test_mode_enabled()) { |
| // This is a browser test or maybe a tast test that called |
| // chrome.EnableStackSampledMetrics(). |
| return true; |
| } |
| #endif |
| |
| // All channels are supported in release builds. |
| return *release_channel != version_info::Channel::UNKNOWN; |
| } |
| |
| #if BUILDFLAG(IS_ANDROID) |
| // The configuration to use for the Android platform. Defined in terms of |
| // DefaultPlatformConfiguration where Android does not differ from the default |
| // case. |
| class AndroidPlatformConfiguration : public DefaultPlatformConfiguration { |
| public: |
| explicit AndroidPlatformConfiguration( |
| bool browser_test_mode_enabled, |
| base::RepeatingCallback<bool(double)> is_enabled_on_dev_callback); |
| |
| RelativePopulations GetEnableRates( |
| std::optional<version_info::Channel> release_channel) const override; |
| |
| double GetChildProcessPerExecutionEnableFraction( |
| sampling_profiler::ProfilerProcessType process) const override; |
| |
| std::optional<sampling_profiler::ProfilerProcessType> ChooseEnabledProcess() |
| const override; |
| |
| bool IsEnabledForThread( |
| sampling_profiler::ProfilerProcessType process, |
| sampling_profiler::ProfilerThreadType thread, |
| std::optional<version_info::Channel> release_channel) const override; |
| |
| bool IsSupportedForChannel( |
| std::optional<version_info::Channel> release_channel) const override; |
| |
| private: |
| // Whether profiling is enabled on a thread type for Android DEV channel. |
| const base::flat_map<sampling_profiler::ProfilerThreadType, bool> |
| thread_enabled_on_dev_; |
| }; |
| |
| AndroidPlatformConfiguration::AndroidPlatformConfiguration( |
| bool browser_test_mode_enabled, |
| base::RepeatingCallback<bool(double)> is_enabled_on_dev_callback) |
| : DefaultPlatformConfiguration(browser_test_mode_enabled), |
| thread_enabled_on_dev_( |
| base::MakeFlatMap<sampling_profiler::ProfilerThreadType, bool>( |
| []() { |
| std::vector<sampling_profiler::ProfilerThreadType> threads; |
| for (int i = 0; |
| i <= static_cast<int>( |
| sampling_profiler::ProfilerThreadType::kMax); |
| i++) { |
| threads.push_back( |
| static_cast<sampling_profiler::ProfilerThreadType>(i)); |
| } |
| return threads; |
| }(), |
| {}, |
| [&](sampling_profiler::ProfilerThreadType thread) { |
| // Only enable 25% of threads on Dev channel as analysis |
| // shows 25% thread enable rate will give us sufficient |
| // resolution (100us). |
| return std::make_pair(thread, |
| is_enabled_on_dev_callback.Run(0.25)); |
| })) {} |
| |
| ThreadProfilerPlatformConfiguration::RelativePopulations |
| AndroidPlatformConfiguration::GetEnableRates( |
| std::optional<version_info::Channel> release_channel) const { |
| // Always enable profiling in local/CQ builds or browser test mode. |
| if (!release_channel.has_value() || browser_test_mode_enabled()) { |
| return RelativePopulations{0.0, 100.0, 0.0}; |
| } |
| |
| CHECK(*release_channel != version_info::Channel::UNKNOWN); |
| |
| if (*release_channel == version_info::Channel::STABLE) { |
| // Only enable for arm64, as this does not require DFM installation. |
| #if defined(ARCH_CPU_ARM64) |
| // For 100% of population |
| // - 1/2 within the subgroup, i.e. 50.0% of total population, enable |
| // profiling. |
| // - 1/2 within the subgroup, disable profiling. |
| // This results a total of 0.00005% enable rate. |
| static constexpr double experiment_rate = 0.0001; |
| return RelativePopulations{100.0 - experiment_rate, 0.0, experiment_rate}; |
| #else |
| // Don't enable for arm32. |
| return RelativePopulations{100.0, 0.0, 0.0}; |
| #endif |
| } |
| |
| if (*release_channel == version_info::Channel::BETA) { |
| // For 100% of population |
| // - 1/2 within the subgroup, i.e. 50.0% of total population, enable |
| // profiling. |
| // - 1/2 within the subgroup, disable profiling. |
| // This results a total of 50.0% enable rate. |
| return RelativePopulations{0.0, 0.0, 100.0}; |
| } |
| |
| // For 100% of population |
| // - 1/2 within the subgroup, i.e. 50.0% of total population, enable |
| // profiling. |
| // - 1/2 within the subgroup, disable profiling. |
| // This results a total of 50.0% enable rate. |
| return RelativePopulations{0.0, 0.0, 100.0}; |
| } |
| |
| double AndroidPlatformConfiguration::GetChildProcessPerExecutionEnableFraction( |
| sampling_profiler::ProfilerProcessType process) const { |
| // Unconditionally profile child processes that match ChooseEnabledProcess(). |
| return 1.0; |
| } |
| |
| std::optional<sampling_profiler::ProfilerProcessType> |
| AndroidPlatformConfiguration::ChooseEnabledProcess() const { |
| // Weights are set such that we will receive similar amount of data from |
| // each process type. The value is calculated based on Canary/Dev channel |
| // data collected when all process are sampled. |
| const struct { |
| sampling_profiler::ProfilerProcessType process; |
| int weight; |
| } process_enable_weights[] = { |
| {sampling_profiler::ProfilerProcessType::kBrowser, 50}, |
| {sampling_profiler::ProfilerProcessType::kGpu, 40}, |
| {sampling_profiler::ProfilerProcessType::kRenderer, 10}, |
| }; |
| |
| int total_weight = 0; |
| for (const auto& process_enable_weight : process_enable_weights) { |
| total_weight += process_enable_weight.weight; |
| } |
| DCHECK_EQ(100, total_weight); |
| |
| int chosen = base::RandInt(0, total_weight - 1); // Max is inclusive. |
| int cumulative_weight = 0; |
| for (const auto& process_enable_weight : process_enable_weights) { |
| if (chosen >= cumulative_weight && |
| chosen < cumulative_weight + process_enable_weight.weight) { |
| return process_enable_weight.process; |
| } |
| cumulative_weight += process_enable_weight.weight; |
| } |
| NOTREACHED(); |
| } |
| |
| bool AndroidPlatformConfiguration::IsEnabledForThread( |
| sampling_profiler::ProfilerProcessType process, |
| sampling_profiler::ProfilerThreadType thread, |
| std::optional<version_info::Channel> release_channel) const { |
| if (!DefaultPlatformConfiguration::IsEnabledForThread(process, thread, |
| release_channel)) { |
| return false; |
| } |
| |
| if (!release_channel.has_value() || browser_test_mode_enabled()) { |
| return true; |
| } |
| |
| switch (*release_channel) { |
| // TODO(crbug.com/40287243): Adjust thread-level enable rate for beta |
| // channel based on the data volume after launch. Temporarily use the same |
| // thread-level enable rate as dev channel. |
| case version_info::Channel::BETA: |
| case version_info::Channel::DEV: { |
| const auto entry = thread_enabled_on_dev_.find(thread); |
| CHECK(entry != thread_enabled_on_dev_.end()); |
| return entry->second; |
| } |
| case version_info::Channel::CANARY: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool AndroidPlatformConfiguration::IsSupportedForChannel( |
| std::optional<version_info::Channel> release_channel) const { |
| // The profiler is always supported for local builds and the CQ. |
| if (!release_channel) { |
| return true; |
| } |
| |
| // Canary, dev, beta, stable channels are supported in release builds, with |
| // stable only support on arm64. |
| switch (*release_channel) { |
| case version_info::Channel::CANARY: |
| case version_info::Channel::DEV: |
| case version_info::Channel::BETA: |
| #if defined(ARCH_CPU_ARM64) |
| case version_info::Channel::STABLE: |
| #endif |
| return true; |
| |
| default: |
| return false; |
| } |
| } |
| |
| #endif // BUILDFLAG(IS_ANDROID) |
| |
| } // namespace |
| |
| // static |
| std::unique_ptr<ThreadProfilerPlatformConfiguration> |
| ThreadProfilerPlatformConfiguration::Create( |
| bool browser_test_mode_enabled, |
| base::RepeatingCallback<bool(double)> is_enabled_on_dev_callback) { |
| #if BUILDFLAG(IS_ANDROID) |
| return std::make_unique<AndroidPlatformConfiguration>( |
| browser_test_mode_enabled, is_enabled_on_dev_callback); |
| #else |
| return std::make_unique<DefaultPlatformConfiguration>( |
| browser_test_mode_enabled); |
| #endif |
| } |
| |
| bool ThreadProfilerPlatformConfiguration::IsSupported( |
| std::optional<version_info::Channel> release_channel) const { |
| return base::StackSamplingProfiler::IsSupportedForCurrentPlatform() && |
| IsSupportedForChannel(release_channel); |
| } |
| |
| // static |
| bool ThreadProfilerPlatformConfiguration::IsEnabled( |
| double enabled_probability) { |
| DCHECK_GE(enabled_probability, 0.0); |
| DCHECK_LE(enabled_probability, 1.0); |
| return base::RandDouble() < enabled_probability; |
| } |