chromium/chrome/common/profiler/thread_profiler_platform_configuration.cc

// 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/functional/callback.h"
#include "base/notreached.h"
#include "base/profiler/process_type.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"

namespace {

// The default configuration to use in the absence of special circumstances on a
// specific platform.
class DefaultPlatformConfiguration
    : public ThreadProfilerPlatformConfiguration {};

DefaultPlatformConfiguration::DefaultPlatformConfiguration(
    bool browser_test_mode_enabled)
    :{}

ThreadProfilerPlatformConfiguration::RelativePopulations
DefaultPlatformConfiguration::GetEnableRates(
    std::optional<version_info::Channel> release_channel) const {}

double DefaultPlatformConfiguration::GetChildProcessPerExecutionEnableFraction(
    base::ProfilerProcessType process) const {}

std::optional<base::ProfilerProcessType>
DefaultPlatformConfiguration::ChooseEnabledProcess() const {}

bool DefaultPlatformConfiguration::IsEnabledForThread(
    base::ProfilerProcessType process,
    base::ProfilerThreadType thread,
    std::optional<version_info::Channel> release_channel) const {}

bool DefaultPlatformConfiguration::IsSupportedForChannel(
    std::optional<version_info::Channel> release_channel) const {}

#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(
      base::ProfilerProcessType process) const override;

  std::optional<base::ProfilerProcessType> ChooseEnabledProcess()
      const override;

  bool IsEnabledForThread(
      base::ProfilerProcessType process,
      base::ProfilerThreadType thread,
      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<base::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<base::ProfilerThreadType, bool>(
          []() {
            std::vector<base::ProfilerThreadType> threads;
            for (int i = 0;
                 i <= static_cast<int>(base::ProfilerThreadType::kMax); i++) {
              threads.push_back(static_cast<base::ProfilerThreadType>(i));
            }
            return threads;
          }(),
          {},
          [&](base::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, 100, 0};
  }

  CHECK(*release_channel == version_info::Channel::CANARY ||
        *release_channel == version_info::Channel::DEV ||
        *release_channel == version_info::Channel::BETA);

  if (*release_channel == version_info::Channel::BETA) {
    // TODO(crbug.com/40191622): Enable for 100% of the population.
    return RelativePopulations{25, 0, 75};
  }
  // For 100% of population
  // - 1/3 within the subgroup, i.e. 50% of total population, enable profiling.
  // - 1/3 within the subgroup, i.e. 50% of total population, enable profiling
  //   with thread pool unwinding.
  // - 1/3 within the subgroup, disable profiling.
  return RelativePopulations{0, 1, 99};
}

double AndroidPlatformConfiguration::GetChildProcessPerExecutionEnableFraction(
    base::ProfilerProcessType process) const {
  // Unconditionally profile child processes that match ChooseEnabledProcess().
  return 1.0;
}

std::optional<base::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 {
    base::ProfilerProcessType process;
    int weight;
  } process_enable_weights[] = {
      {base::ProfilerProcessType::kBrowser, 50},
      {base::ProfilerProcessType::kGpu, 40},
      {base::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_IN_MIGRATION();
  return std::nullopt;
}

bool AndroidPlatformConfiguration::IsEnabledForThread(
    base::ProfilerProcessType process,
    base::ProfilerThreadType thread,
    std::optional<version_info::Channel> release_channel) const {
#if BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_ARM64)
  // For now, we only enable SSM in the Browser process and Main thread on
  // Android 64, since Libunwindstack doesn't support JavaScript.
  if (!(process == base::ProfilerProcessType::kBrowser &&
        thread == base::ProfilerThreadType::kMain)) {
    return false;
  }
#endif
  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;
  }
}

#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) {}

bool ThreadProfilerPlatformConfiguration::IsSupported(
    std::optional<version_info::Channel> release_channel) const {}

// static
bool ThreadProfilerPlatformConfiguration::IsEnabled(
    double enabled_probability) {}