Commit a2483f8c authored by Mikhail Khokhlov's avatar Mikhail Khokhlov Committed by Commit Bot

content: Enforce CPU affinity on Android

As a part of the experiment to run all execution on little cores
we want to make sure that the cpu affinity doesn't change at runtime,
e.g. when Chrome goes back from background. To do this, we introduce
an interface in content that allows to enforce affinity by checking
it on a regular basis (every 100 tasks) and setting it back when it
changes.

Change-Id: I766e94af42861870abe3a0079dd335a23462d481
Bug: 1111789
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2418673
Commit-Queue: Mikhail Khokhlov <khokhlov@google.com>
Reviewed-by: default avatarRobert Sesek <rsesek@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarAvi Drissman <avi@chromium.org>
Reviewed-by: default avatarEric Seckler <eseckler@chromium.org>
Cr-Commit-Position: refs/heads/master@{#812027}
parent b5523288
......@@ -11,17 +11,29 @@
namespace base {
bool SetThreadCpuAffinityMode(PlatformThreadId thread_id,
CpuAffinityMode affinity) {
namespace {
const cpu_set_t& AllCores() {
static const cpu_set_t kAllCores = []() {
cpu_set_t set;
memset(&set, 0xff, sizeof(set));
CPU_ZERO(&set);
std::vector<CPU::CoreType> core_types = CPU::GuessCoreTypes();
if (core_types.empty()) {
memset(&set, 0xff, sizeof(set));
} else {
for (size_t index = 0; index < core_types.size(); index++)
CPU_SET(index, &set);
}
return set;
}();
return kAllCores;
}
const cpu_set_t& LittleCores() {
static const cpu_set_t kLittleCores = []() {
std::vector<CPU::CoreType> core_types = CPU::GuessCoreTypes();
if (core_types.empty())
return kAllCores;
return AllCores();
cpu_set_t set;
CPU_ZERO(&set);
......@@ -32,7 +44,7 @@ bool SetThreadCpuAffinityMode(PlatformThreadId thread_id,
case CPU::CoreType::kSymmetric:
// In the presence of an unknown core type or symmetric architecture,
// fall back to allowing all cores.
return kAllCores;
return AllCores();
case CPU::CoreType::kBigLittle_Little:
case CPU::CoreType::kBigLittleBigger_Little:
CPU_SET(core_index, &set);
......@@ -45,18 +57,51 @@ bool SetThreadCpuAffinityMode(PlatformThreadId thread_id,
}
return set;
}();
return kLittleCores;
}
} // anonymous namespace
bool HasBigCpuCores() {
static const bool kHasBigCores = []() {
std::vector<CPU::CoreType> core_types = CPU::GuessCoreTypes();
if (core_types.empty())
return false;
for (CPU::CoreType core_type : core_types) {
switch (core_type) {
case CPU::CoreType::kUnknown:
case CPU::CoreType::kOther:
case CPU::CoreType::kSymmetric:
return false;
case CPU::CoreType::kBigLittle_Little:
case CPU::CoreType::kBigLittleBigger_Little:
case CPU::CoreType::kBigLittle_Big:
case CPU::CoreType::kBigLittleBigger_Big:
case CPU::CoreType::kBigLittleBigger_Bigger:
return true;
}
}
return false;
}();
return kHasBigCores;
}
bool SetThreadCpuAffinityMode(PlatformThreadId thread_id,
CpuAffinityMode affinity) {
int result = 0;
switch (affinity) {
case CpuAffinityMode::kDefault:
result = sched_setaffinity(thread_id, sizeof(kAllCores), &kAllCores);
case CpuAffinityMode::kDefault: {
const cpu_set_t& all_cores = AllCores();
result = sched_setaffinity(thread_id, sizeof(all_cores), &all_cores);
break;
case CpuAffinityMode::kLittleCoresOnly:
}
case CpuAffinityMode::kLittleCoresOnly: {
const cpu_set_t& little_cores = LittleCores();
result =
sched_setaffinity(thread_id, sizeof(kLittleCores), &kLittleCores);
sched_setaffinity(thread_id, sizeof(little_cores), &little_cores);
break;
}
}
return result == 0;
}
......@@ -75,4 +120,16 @@ bool SetProcessCpuAffinityMode(ProcessHandle process_handle,
return any_threads && result;
}
Optional<CpuAffinityMode> CurrentThreadCpuAffinityMode() {
if (HasBigCpuCores()) {
cpu_set_t set;
sched_getaffinity(PlatformThread::CurrentId(), sizeof(set), &set);
if (CPU_EQUAL(&set, &AllCores()))
return CpuAffinityMode::kDefault;
if (CPU_EQUAL(&set, &LittleCores()))
return CpuAffinityMode::kLittleCoresOnly;
}
return nullopt;
}
} // namespace base
......@@ -28,6 +28,15 @@ BASE_EXPORT bool SetThreadCpuAffinityMode(PlatformThreadId thread_id,
BASE_EXPORT bool SetProcessCpuAffinityMode(ProcessHandle process_handle,
CpuAffinityMode affinity);
// Return true if the current architecture has big or bigger cores.
BASE_EXPORT bool HasBigCpuCores();
// For architectures with big cores, return the affinity mode that matches
// the CPU affinity of the current thread. If no affinity mode exactly matches,
// or if the architecture doesn't have different types of cores,
// return nullopt.
BASE_EXPORT base::Optional<CpuAffinityMode> CurrentThreadCpuAffinityMode();
} // namespace base
#endif // BASE_CPU_AFFINITY_POSIX_H_
......@@ -32,6 +32,7 @@ include_rules = [
"+components/version_info",
"+content/public/app",
"+content/public/browser/browser_main_runner.h",
"+content/public/common",
"+extensions/common/constants.h",
"+headless/public",
"+native_client/src/trusted/service_runtime/osx",
......
......@@ -133,8 +133,8 @@
#include "chrome/browser/android/crash/pure_java_exception_handler.h"
#include "chrome/browser/android/metrics/uma_session_stats.h"
#include "chrome/browser/flags/android/chrome_feature_list.h"
#include "chrome/common/android/cpu_affinity_experiments.h"
#include "chrome/common/chrome_descriptors.h"
#include "content/public/common/cpu_affinity.h"
#include "net/android/network_change_notifier_factory_android.h"
#else // defined(OS_ANDROID)
// Diagnostics is only available on non-android platforms.
......@@ -595,7 +595,10 @@ void ChromeMainDelegate::PostFieldTrialInitialization() {
// For child processes, this requires allowlisting of the sched_setaffinity()
// syscall in the sandbox (baseline_policy_android.cc). When this call is
// removed, the sandbox allowlist should be updated too.
chrome::InitializeCpuAffinityExperiments();
if (base::FeatureList::IsEnabled(
features::kCpuAffinityRestrictToLittleCores)) {
content::EnforceProcessCpuAffinity(base::CpuAffinityMode::kLittleCoresOnly);
}
#endif
#if defined(OS_CHROMEOS)
......
......@@ -402,8 +402,6 @@ static_library("common") {
if (is_android) {
sources -= [ "media_galleries/metadata_types.h" ]
sources += [
"android/cpu_affinity_experiments.cc",
"android/cpu_affinity_experiments.h",
"media/chrome_media_drm_bridge_client.cc",
"media/chrome_media_drm_bridge_client.h",
]
......
// Copyright 2020 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/common/android/cpu_affinity_experiments.h"
#include "base/cpu_affinity_posix.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_functions.h"
#include "base/process/process_handle.h"
#include "chrome/common/chrome_features.h"
namespace chrome {
void InitializeCpuAffinityExperiments() {
if (!base::FeatureList::IsEnabled(
features::kCpuAffinityRestrictToLittleCores)) {
return;
}
// Restrict affinity of all existing threads of the current process. The
// affinity is inherited by any subsequently created thread. While
// InitializeThreadAffinityExperiments() is called early during startup, other
// threads (e.g. Java threads like the RenderThread) may already exist, so
// setting the affinity only for the current thread is not enough here.
bool success = base::SetProcessCpuAffinityMode(
base::GetCurrentProcessHandle(), base::CpuAffinityMode::kLittleCoresOnly);
base::UmaHistogramBoolean(
"Power.CpuAffinityExperiments.ProcessAffinityUpdateSuccess", success);
}
} // namespace chrome
// Copyright 2020 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.
#ifndef CHROME_COMMON_ANDROID_CPU_AFFINITY_EXPERIMENTS_H_
#define CHROME_COMMON_ANDROID_CPU_AFFINITY_EXPERIMENTS_H_
namespace chrome {
// Setup CPU-affinity restriction experiments (e.g. to restrict execution to
// little cores only) for the current process, based on the feature list. Should
// be called during process startup after feature list initialization.
void InitializeCpuAffinityExperiments();
} // namespace chrome
#endif // CHROME_COMMON_ANDROID_CPU_AFFINITY_EXPERIMENTS_H_
......@@ -166,6 +166,7 @@
#if defined(OS_ANDROID)
#include "content/browser/android/browser_startup_controller.h"
#include "content/common/android/cpu_affinity.h"
#endif
namespace content {
......@@ -925,6 +926,10 @@ int ContentMainRunnerImpl::RunServiceManager(MainFunctionParams& main_params,
#if defined(OS_ANDROID)
SetupCpuTimeMetrics();
// For child processes, this requires allowing of the
// sched_setaffinity() syscall in the sandbox (baseline_policy_android.cc).
// When this call is removed, the sandbox allowlist should be updated too.
SetupCpuAffinityPollingOnce();
#endif
if (should_start_service_manager_only)
......
......@@ -28,6 +28,10 @@
#include "base/test/clang_profiling.h"
#endif
#if defined(OS_ANDROID)
#include "content/common/android/cpu_affinity.h"
#endif
namespace content {
namespace {
......@@ -83,6 +87,10 @@ ChildProcess::ChildProcess(base::ThreadPriority io_thread_priority,
#if defined(OS_ANDROID)
SetupCpuTimeMetrics();
// For child processes, this requires allowing of the sched_setaffinity()
// syscall in the sandbox (baseline_policy_android.cc). When this call is
// removed, the sandbox allowlist should be updated too.
SetupCpuAffinityPollingOnce();
#endif
// We can't recover from failing to start the IO thread.
......
......@@ -48,6 +48,8 @@ source_set("common") {
sources = [
"all_messages.h",
"android/cpu_affinity.cc",
"android/cpu_affinity.h",
"android/cpu_time_metrics.cc",
"android/cpu_time_metrics.h",
"android/gin_java_bridge_errors.cc",
......
// Copyright 2020 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 "content/common/android/cpu_affinity.h"
#include "content/public/common/cpu_affinity.h"
#include "base/command_line.h"
#include "base/cpu.h"
#include "base/cpu_affinity_posix.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "base/process/internal_linux.h"
#include "base/process/process_handle.h"
#include "base/task/current_thread.h"
#include "base/task/task_observer.h"
#include "base/threading/thread_task_runner_handle.h"
namespace {
void ApplyProcessCpuAffinityMode(base::CpuAffinityMode mode) {
// Restrict affinity of all existing threads of the current process. The
// affinity is inherited by any subsequently created thread. Other threads may
// already exist even during early startup (e.g. Java threads like
// RenderThread), so setting the affinity only for the current thread is not
// enough here.
bool success =
base::SetProcessCpuAffinityMode(base::GetCurrentProcessHandle(), mode);
base::UmaHistogramBoolean(
"Power.CpuAffinityExperiments.ProcessAffinityUpdateSuccess", success);
}
class CpuAffinityTaskObserver : public base::TaskObserver {
public:
CpuAffinityTaskObserver() {}
static CpuAffinityTaskObserver* GetInstance() {
static base::NoDestructor<CpuAffinityTaskObserver> instance;
return instance.get();
}
void WillProcessTask(const base::PendingTask& pending_task,
bool was_blocked_or_low_priority) override {}
void DidProcessTask(const base::PendingTask& pending_task) override {
++task_counter_;
if (task_counter_ == kUpdateAfterEveryNTasks) {
if (enforced_mode_ &&
*enforced_mode_ != base::CurrentThreadCpuAffinityMode()) {
ApplyProcessCpuAffinityMode(*enforced_mode_);
}
task_counter_ = 0;
}
}
void InitializeCpuAffinity(base::CpuAffinityMode mode) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// For now, affinity modes only have an effect on big.LITTLE architectures.
if (!base::HasBigCpuCores())
return;
enforced_mode_ = mode;
ApplyProcessCpuAffinityMode(mode);
// Set up task observer if it's already possible. Otherwise it will be set
// up after thread initialization (see app/content_main_runner_impl.cc and
// child/child_process.cc).
if (base::CurrentThread::IsSet())
SetupCpuAffinityPolling();
}
void SetupCpuAffinityPollingOnce() {
// The polling should be set up once from the main thread. Subsequent calls
// from other threads should be ignored.
if (did_call_setup_)
return;
SetupCpuAffinityPolling();
did_call_setup_ = true;
}
private:
void SetupCpuAffinityPolling() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!base::HasBigCpuCores() || !enforced_mode_ || did_setup_polling_)
return;
base::CurrentThread::Get()->AddTaskObserver(this);
did_setup_polling_ = true;
}
SEQUENCE_CHECKER(sequence_checker_);
static constexpr int kUpdateAfterEveryNTasks = 100;
int task_counter_ = 0;
bool did_setup_polling_ = false;
bool did_call_setup_ = false;
base::Optional<base::CpuAffinityMode> enforced_mode_;
};
} // anonymous namespace
namespace content {
void EnforceProcessCpuAffinity(base::CpuAffinityMode mode) {
CpuAffinityTaskObserver::GetInstance()->InitializeCpuAffinity(mode);
}
void SetupCpuAffinityPollingOnce() {
CpuAffinityTaskObserver::GetInstance()->SetupCpuAffinityPollingOnce();
}
} // namespace content
// Copyright 2020 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.
#ifndef CONTENT_COMMON_ANDROID_CPU_AFFINITY_H_
#define CONTENT_COMMON_ANDROID_CPU_AFFINITY_H_
namespace content {
// Set up a regular polling to check that the current CPU affinity is set to
// the right mode and update it if it's not. The check is implemented as a
// TaskObserver that runs every 100th main thread task.
// This function should be called from the main thread during the initialization
// of the process. Subsequent calls from other threads will have no effect.
void SetupCpuAffinityPollingOnce();
} // namespace content
#endif // CONTENT_COMMON_ANDROID_CPU_AFFINITY_H_
......@@ -247,6 +247,7 @@ source_set("common_sources") {
}
if (is_android) {
sources += [ "cpu_affinity.h" ]
deps += [ "//content/public/android:jni" ]
}
......
// Copyright 2020 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.
#ifndef CONTENT_PUBLIC_COMMON_CPU_AFFINITY_H_
#define CONTENT_PUBLIC_COMMON_CPU_AFFINITY_H_
#include "base/cpu_affinity_posix.h"
#include "content/common/content_export.h"
namespace content {
// Enforce CPU affinity of the current process based on the CPU affinity mode.
// Use this to set up CPU-affinity restriction experiments (e.g. to restrict
// execution to little cores only). Should be called on the process's main
// thread during process startup after feature list initialization.
// The affinity might change at runtime (e.g. after Chrome goes back from
// background), so the content layer will set up a polling mechanism to enforce
// the given mode.
CONTENT_EXPORT void EnforceProcessCpuAffinity(base::CpuAffinityMode mode);
} // namespace content
#endif // CONTENT_PUBLIC_COMMON_CPU_AFFINITY_H_
......@@ -112,9 +112,11 @@ ResultExpr BaselinePolicyAndroid::EvaluateSyscall(int sysno) const {
case __NR_openat:
case __NR_pwrite64:
case __NR_rt_sigtimedwait:
// sched_setaffinity() is required for an experiment to schedule all
// Chromium threads onto LITTLE cores (crbug.com/1111789). Should be removed
// or reconsidered once the experiment is complete.
// sched_getaffinity() and sched_setaffinity() are required for an
// experiment to schedule all Chromium threads onto LITTLE cores
// (crbug.com/1111789). Should be removed or reconsidered once
// the experiment is complete.
case __NR_sched_getaffinity:
case __NR_sched_setaffinity:
case __NR_sched_getparam:
case __NR_sched_getscheduler:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment