510 lines
16 KiB
C++
510 lines
16 KiB
C++
// Copyright 2012 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "base/threading/platform_thread.h"
|
|
|
|
#include <errno.h>
|
|
#include <sched.h>
|
|
#include <stddef.h>
|
|
#include <cstdint>
|
|
#include <atomic>
|
|
|
|
#include "base/base_switches.h"
|
|
#include "base/command_line.h"
|
|
#include "base/compiler_specific.h"
|
|
#include "base/feature_list.h"
|
|
#include "base/files/file_util.h"
|
|
#include "base/lazy_instance.h"
|
|
#include "base/logging.h"
|
|
#include "base/metrics/field_trial_params.h"
|
|
#include "base/notreached.h"
|
|
#include "base/process/internal_linux.h"
|
|
#include "base/strings/string_number_conversions.h"
|
|
#include "base/strings/stringprintf.h"
|
|
#include "base/threading/platform_thread_internal_posix.h"
|
|
#include "base/threading/thread_id_name_manager.h"
|
|
#include "base/threading/thread_type_delegate.h"
|
|
#include "build/build_config.h"
|
|
#include "third_party/abseil-cpp/absl/types/optional.h"
|
|
|
|
#if !BUILDFLAG(IS_NACL) && !BUILDFLAG(IS_AIX)
|
|
#include <pthread.h>
|
|
#include <sys/prctl.h>
|
|
#include <sys/resource.h>
|
|
#include <sys/time.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
namespace base {
|
|
|
|
#if BUILDFLAG(IS_CHROMEOS)
|
|
BASE_FEATURE(kSchedUtilHints,
|
|
"SchedUtilHints",
|
|
base::FEATURE_ENABLED_BY_DEFAULT);
|
|
#endif
|
|
|
|
namespace {
|
|
|
|
#if !BUILDFLAG(IS_NACL)
|
|
ThreadTypeDelegate* g_thread_type_delegate = nullptr;
|
|
#endif
|
|
|
|
#if BUILDFLAG(IS_CHROMEOS)
|
|
std::atomic<bool> g_use_sched_util(true);
|
|
std::atomic<bool> g_scheduler_hints_adjusted(false);
|
|
|
|
// When a device doesn't specify uclamp values via chrome switches,
|
|
// default boosting for urgent tasks is hardcoded here as 20%.
|
|
// Higher values can lead to higher power consumption thus this value
|
|
// is chosen conservatively where it does not show noticeable
|
|
// power usage increased from several perf/power tests.
|
|
const int kSchedulerBoostDef = 20;
|
|
const int kSchedulerLimitDef = 100;
|
|
const bool kSchedulerUseLatencyTuneDef = true;
|
|
|
|
int g_scheduler_boost_adj;
|
|
int g_scheduler_limit_adj;
|
|
bool g_scheduler_use_latency_tune_adj;
|
|
|
|
#if !BUILDFLAG(IS_NACL) && !BUILDFLAG(IS_AIX)
|
|
|
|
// Defined by linux uclamp ABI of sched_setattr().
|
|
const uint32_t kSchedulerUclampMin = 0;
|
|
const uint32_t kSchedulerUclampMax = 1024;
|
|
|
|
// sched_attr is used to set scheduler attributes for Linux. It is not a POSIX
|
|
// struct and glibc does not expose it.
|
|
struct sched_attr {
|
|
uint32_t size;
|
|
|
|
uint32_t sched_policy;
|
|
uint64_t sched_flags;
|
|
|
|
/* SCHED_NORMAL, SCHED_BATCH */
|
|
__s32 sched_nice;
|
|
|
|
/* SCHED_FIFO, SCHED_RR */
|
|
uint32_t sched_priority;
|
|
|
|
/* SCHED_DEADLINE */
|
|
uint64_t sched_runtime;
|
|
uint64_t sched_deadline;
|
|
uint64_t sched_period;
|
|
|
|
/* Utilization hints */
|
|
uint32_t sched_util_min;
|
|
uint32_t sched_util_max;
|
|
};
|
|
|
|
#if !defined(__NR_sched_setattr)
|
|
#if defined(__x86_64__)
|
|
#define __NR_sched_setattr 314
|
|
#define __NR_sched_getattr 315
|
|
#elif defined(__i386__)
|
|
#define __NR_sched_setattr 351
|
|
#define __NR_sched_getattr 352
|
|
#elif defined(__arm__)
|
|
#define __NR_sched_setattr 380
|
|
#define __NR_sched_getattr 381
|
|
#elif defined(__aarch64__)
|
|
#define __NR_sched_setattr 274
|
|
#define __NR_sched_getattr 275
|
|
#else
|
|
#error "We don't have an __NR_sched_setattr for this architecture."
|
|
#endif
|
|
#endif
|
|
|
|
#if !defined(SCHED_FLAG_UTIL_CLAMP_MIN)
|
|
#define SCHED_FLAG_UTIL_CLAMP_MIN 0x20
|
|
#endif
|
|
|
|
#if !defined(SCHED_FLAG_UTIL_CLAMP_MAX)
|
|
#define SCHED_FLAG_UTIL_CLAMP_MAX 0x40
|
|
#endif
|
|
|
|
long sched_getattr(pid_t pid,
|
|
const struct sched_attr* attr,
|
|
unsigned int size,
|
|
unsigned int flags) {
|
|
return syscall(__NR_sched_getattr, pid, attr, size, flags);
|
|
}
|
|
|
|
long sched_setattr(pid_t pid,
|
|
const struct sched_attr* attr,
|
|
unsigned int flags) {
|
|
return syscall(__NR_sched_setattr, pid, attr, flags);
|
|
}
|
|
#endif // !BUILDFLAG(IS_NACL) && !BUILDFLAG(IS_AIX)
|
|
#endif // BUILDFLAG(IS_CHROMEOS)
|
|
|
|
#if !BUILDFLAG(IS_NACL)
|
|
const FilePath::CharType kCgroupDirectory[] =
|
|
FILE_PATH_LITERAL("/sys/fs/cgroup");
|
|
|
|
FilePath ThreadTypeToCgroupDirectory(const FilePath& cgroup_filepath,
|
|
ThreadType thread_type) {
|
|
switch (thread_type) {
|
|
case ThreadType::kBackground:
|
|
case ThreadType::kUtility:
|
|
case ThreadType::kResourceEfficient:
|
|
return cgroup_filepath.Append(FILE_PATH_LITERAL("non-urgent"));
|
|
case ThreadType::kDefault:
|
|
return cgroup_filepath;
|
|
case ThreadType::kCompositing:
|
|
#if BUILDFLAG(IS_CHROMEOS)
|
|
// On ChromeOS, kCompositing is also considered urgent.
|
|
return cgroup_filepath.Append(FILE_PATH_LITERAL("urgent"));
|
|
#else
|
|
// TODO(1329208): Experiment with bringing IS_LINUX inline with
|
|
// IS_CHROMEOS.
|
|
return cgroup_filepath;
|
|
#endif
|
|
case ThreadType::kDisplayCritical:
|
|
case ThreadType::kRealtimeAudio:
|
|
return cgroup_filepath.Append(FILE_PATH_LITERAL("urgent"));
|
|
}
|
|
NOTREACHED();
|
|
return FilePath();
|
|
}
|
|
|
|
void SetThreadCgroup(PlatformThreadId thread_id,
|
|
const FilePath& cgroup_directory) {
|
|
FilePath tasks_filepath = cgroup_directory.Append(FILE_PATH_LITERAL("tasks"));
|
|
std::string tid = NumberToString(thread_id);
|
|
// TODO(crbug.com/1333521): Remove cast.
|
|
const int size = static_cast<int>(tid.size());
|
|
int bytes_written = WriteFile(tasks_filepath, tid.data(), size);
|
|
if (bytes_written != size) {
|
|
DVLOG(1) << "Failed to add " << tid << " to " << tasks_filepath.value();
|
|
}
|
|
}
|
|
|
|
void SetThreadCgroupForThreadType(PlatformThreadId thread_id,
|
|
const FilePath& cgroup_filepath,
|
|
ThreadType thread_type) {
|
|
// Append "chrome" suffix.
|
|
FilePath cgroup_directory = ThreadTypeToCgroupDirectory(
|
|
cgroup_filepath.Append(FILE_PATH_LITERAL("chrome")), thread_type);
|
|
|
|
// Silently ignore request if cgroup directory doesn't exist.
|
|
if (!DirectoryExists(cgroup_directory))
|
|
return;
|
|
|
|
SetThreadCgroup(thread_id, cgroup_directory);
|
|
}
|
|
|
|
#if BUILDFLAG(IS_CHROMEOS)
|
|
// thread_id should always be the value in the root PID namespace (see
|
|
// FindThreadID).
|
|
void SetThreadLatencySensitivity(ProcessId process_id,
|
|
PlatformThreadId thread_id,
|
|
ThreadType thread_type) {
|
|
struct sched_attr attr;
|
|
bool is_urgent = false;
|
|
int boost_percent, limit_percent;
|
|
int latency_sensitive_urgent;
|
|
|
|
// Scheduler boost defaults to true unless disabled.
|
|
if (!g_use_sched_util.load())
|
|
return;
|
|
|
|
// FieldTrial API can be called only once features were parsed.
|
|
if (g_scheduler_hints_adjusted.load()) {
|
|
boost_percent = g_scheduler_boost_adj;
|
|
limit_percent = g_scheduler_limit_adj;
|
|
latency_sensitive_urgent = g_scheduler_use_latency_tune_adj;
|
|
} else {
|
|
boost_percent = kSchedulerBoostDef;
|
|
limit_percent = kSchedulerLimitDef;
|
|
latency_sensitive_urgent = kSchedulerUseLatencyTuneDef;
|
|
}
|
|
|
|
// The thread_id passed in here is either 0 (in which case we ste for current
|
|
// thread), or is a tid that is not the NS tid but the global one. The
|
|
// conversion from NS tid to global tid is done by the callers using
|
|
// FindThreadID().
|
|
std::string thread_dir;
|
|
if (thread_id)
|
|
thread_dir = base::StringPrintf("/proc/%d/task/%d/", process_id, thread_id);
|
|
else
|
|
thread_dir = "/proc/thread-self/";
|
|
|
|
// Silently ignore request if thread directory doesn't exist.
|
|
if (!DirectoryExists(FilePath(thread_dir)))
|
|
return;
|
|
|
|
FilePath latency_sensitive_file = FilePath(thread_dir + "latency_sensitive");
|
|
|
|
if (!PathExists(latency_sensitive_file))
|
|
return;
|
|
|
|
// Silently ignore if getattr fails due to sandboxing.
|
|
if (sched_getattr(thread_id, &attr, sizeof(attr), 0) == -1 ||
|
|
attr.size != sizeof(attr))
|
|
return;
|
|
|
|
switch (thread_type) {
|
|
case ThreadType::kBackground:
|
|
case ThreadType::kUtility:
|
|
case ThreadType::kResourceEfficient:
|
|
case ThreadType::kDefault:
|
|
break;
|
|
case ThreadType::kCompositing:
|
|
case ThreadType::kDisplayCritical:
|
|
// Compositing and display critical threads need a boost for consistent 60
|
|
// fps.
|
|
[[fallthrough]];
|
|
case ThreadType::kRealtimeAudio:
|
|
is_urgent = true;
|
|
break;
|
|
}
|
|
|
|
if (is_urgent && latency_sensitive_urgent) {
|
|
PLOG_IF(ERROR, !WriteFile(latency_sensitive_file, "1", 1))
|
|
<< "Failed to write latency file.\n";
|
|
} else {
|
|
PLOG_IF(ERROR, !WriteFile(latency_sensitive_file, "0", 1))
|
|
<< "Failed to write latency file.\n";
|
|
}
|
|
|
|
attr.sched_flags |= SCHED_FLAG_UTIL_CLAMP_MIN;
|
|
attr.sched_flags |= SCHED_FLAG_UTIL_CLAMP_MAX;
|
|
|
|
if (is_urgent) {
|
|
attr.sched_util_min =
|
|
(saturated_cast<uint32_t>(boost_percent) * kSchedulerUclampMax + 50) /
|
|
100;
|
|
attr.sched_util_max = kSchedulerUclampMax;
|
|
} else {
|
|
attr.sched_util_min = kSchedulerUclampMin;
|
|
attr.sched_util_max =
|
|
(saturated_cast<uint32_t>(limit_percent) * kSchedulerUclampMax + 50) /
|
|
100;
|
|
}
|
|
|
|
DCHECK_GE(attr.sched_util_min, kSchedulerUclampMin);
|
|
DCHECK_LE(attr.sched_util_max, kSchedulerUclampMax);
|
|
|
|
attr.size = sizeof(struct sched_attr);
|
|
if (sched_setattr(thread_id, &attr, 0) == -1) {
|
|
// We log it as an error because, if the PathExists above succeeded, we
|
|
// expect this syscall to also work since the kernel is new'ish.
|
|
PLOG_IF(ERROR, errno != E2BIG)
|
|
<< "Failed to set sched_util_min, performance may be effected.\n";
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void SetThreadCgroupsForThreadType(PlatformThreadId thread_id,
|
|
ThreadType thread_type) {
|
|
FilePath cgroup_filepath(kCgroupDirectory);
|
|
SetThreadCgroupForThreadType(
|
|
thread_id, cgroup_filepath.Append(FILE_PATH_LITERAL("cpuset")),
|
|
thread_type);
|
|
SetThreadCgroupForThreadType(
|
|
thread_id, cgroup_filepath.Append(FILE_PATH_LITERAL("schedtune")),
|
|
thread_type);
|
|
}
|
|
#endif
|
|
} // namespace
|
|
|
|
namespace internal {
|
|
|
|
namespace {
|
|
#if !BUILDFLAG(IS_NACL)
|
|
const struct sched_param kRealTimePrio = {8};
|
|
#endif
|
|
} // namespace
|
|
|
|
const ThreadPriorityToNiceValuePairForTest
|
|
kThreadPriorityToNiceValueMapForTest[5] = {
|
|
{ThreadPriorityForTest::kRealtimeAudio, -10},
|
|
{ThreadPriorityForTest::kDisplay, -8},
|
|
{ThreadPriorityForTest::kNormal, 0},
|
|
{ThreadPriorityForTest::kUtility, 1},
|
|
{ThreadPriorityForTest::kBackground, 10},
|
|
};
|
|
|
|
const ThreadTypeToNiceValuePair kThreadTypeToNiceValueMap[7] = {
|
|
{ThreadType::kBackground, 10}, {ThreadType::kUtility, 1},
|
|
{ThreadType::kResourceEfficient, 0}, {ThreadType::kDefault, 0},
|
|
#if BUILDFLAG(IS_CHROMEOS)
|
|
{ThreadType::kCompositing, -8},
|
|
#else
|
|
// TODO(1329208): Experiment with bringing IS_LINUX inline with IS_CHROMEOS.
|
|
{ThreadType::kCompositing, 0},
|
|
#endif
|
|
{ThreadType::kDisplayCritical, -8}, {ThreadType::kRealtimeAudio, -10},
|
|
};
|
|
|
|
bool CanSetThreadTypeToRealtimeAudio() {
|
|
#if !BUILDFLAG(IS_NACL)
|
|
// A non-zero soft-limit on RLIMIT_RTPRIO is required to be allowed to invoke
|
|
// pthread_setschedparam in SetCurrentThreadTypeForPlatform().
|
|
struct rlimit rlim;
|
|
return getrlimit(RLIMIT_RTPRIO, &rlim) != 0 && rlim.rlim_cur != 0;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool SetCurrentThreadTypeForPlatform(ThreadType thread_type,
|
|
MessagePumpType pump_type_hint) {
|
|
#if !BUILDFLAG(IS_NACL)
|
|
const PlatformThreadId tid = PlatformThread::CurrentId();
|
|
|
|
if (g_thread_type_delegate &&
|
|
g_thread_type_delegate->HandleThreadTypeChange(tid, thread_type)) {
|
|
return true;
|
|
}
|
|
|
|
// For legacy schedtune interface
|
|
SetThreadCgroupsForThreadType(tid, thread_type);
|
|
|
|
#if BUILDFLAG(IS_CHROMEOS)
|
|
// For upstream uclamp interface. We try both legacy (schedtune, as done
|
|
// earlier) and upstream (uclamp) interfaces, and whichever succeeds wins.
|
|
SetThreadLatencySensitivity(0 /* ignore */, 0 /* thread-self */, thread_type);
|
|
#endif
|
|
|
|
return thread_type == ThreadType::kRealtimeAudio &&
|
|
pthread_setschedparam(pthread_self(), SCHED_RR, &kRealTimePrio) == 0;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
absl::optional<ThreadPriorityForTest>
|
|
GetCurrentThreadPriorityForPlatformForTest() {
|
|
#if !BUILDFLAG(IS_NACL)
|
|
int maybe_sched_rr = 0;
|
|
struct sched_param maybe_realtime_prio = {0};
|
|
if (pthread_getschedparam(pthread_self(), &maybe_sched_rr,
|
|
&maybe_realtime_prio) == 0 &&
|
|
maybe_sched_rr == SCHED_RR &&
|
|
maybe_realtime_prio.sched_priority == kRealTimePrio.sched_priority) {
|
|
return absl::make_optional(ThreadPriorityForTest::kRealtimeAudio);
|
|
}
|
|
#endif
|
|
return absl::nullopt;
|
|
}
|
|
|
|
} // namespace internal
|
|
|
|
// static
|
|
void PlatformThread::SetName(const std::string& name) {
|
|
ThreadIdNameManager::GetInstance()->SetName(name);
|
|
|
|
#if !BUILDFLAG(IS_NACL) && !BUILDFLAG(IS_AIX)
|
|
// On linux we can get the thread names to show up in the debugger by setting
|
|
// the process name for the LWP. We don't want to do this for the main
|
|
// thread because that would rename the process, causing tools like killall
|
|
// to stop working.
|
|
if (PlatformThread::CurrentId() == getpid())
|
|
return;
|
|
|
|
// http://0pointer.de/blog/projects/name-your-threads.html
|
|
// Set the name for the LWP (which gets truncated to 15 characters).
|
|
// Note that glibc also has a 'pthread_setname_np' api, but it may not be
|
|
// available everywhere and it's only benefit over using prctl directly is
|
|
// that it can set the name of threads other than the current thread.
|
|
int err = prctl(PR_SET_NAME, name.c_str());
|
|
// We expect EPERM failures in sandboxed processes, just ignore those.
|
|
if (err < 0 && errno != EPERM)
|
|
DPLOG(ERROR) << "prctl(PR_SET_NAME)";
|
|
#endif // !BUILDFLAG(IS_NACL) && !BUILDFLAG(IS_AIX)
|
|
}
|
|
|
|
#if !BUILDFLAG(IS_NACL)
|
|
// static
|
|
void PlatformThread::SetThreadTypeDelegate(ThreadTypeDelegate* delegate) {
|
|
// A component cannot override a delegate set by another component, thus
|
|
// disallow setting a delegate when one already exists.
|
|
DCHECK(!g_thread_type_delegate || !delegate);
|
|
|
|
g_thread_type_delegate = delegate;
|
|
}
|
|
#endif
|
|
|
|
#if !BUILDFLAG(IS_NACL) && !BUILDFLAG(IS_AIX)
|
|
// static
|
|
void PlatformThread::SetThreadType(ProcessId process_id,
|
|
PlatformThreadId thread_id,
|
|
ThreadType thread_type) {
|
|
// For legacy schedtune interface
|
|
SetThreadCgroupsForThreadType(thread_id, thread_type);
|
|
|
|
#if BUILDFLAG(IS_CHROMEOS)
|
|
// For upstream uclamp interface. We try both legacy (schedtune, as done
|
|
// earlier) and upstream (uclamp) interfaces, and whichever succeeds wins.
|
|
SetThreadLatencySensitivity(process_id, thread_id, thread_type);
|
|
#endif
|
|
|
|
const int nice_setting = internal::ThreadTypeToNiceValue(thread_type);
|
|
if (setpriority(PRIO_PROCESS, static_cast<id_t>(thread_id), nice_setting)) {
|
|
DVPLOG(1) << "Failed to set nice value of thread (" << thread_id << ") to "
|
|
<< nice_setting;
|
|
}
|
|
}
|
|
#endif // !BUILDFLAG(IS_NACL) && !BUILDFLAG(IS_AIX)
|
|
|
|
#if BUILDFLAG(IS_CHROMEOS)
|
|
void PlatformThread::InitFeaturesPostFieldTrial() {
|
|
DCHECK(FeatureList::GetInstance());
|
|
if (!FeatureList::IsEnabled(kSchedUtilHints)) {
|
|
g_use_sched_util.store(false);
|
|
return;
|
|
}
|
|
|
|
int boost_def = kSchedulerBoostDef;
|
|
|
|
if (CommandLine::ForCurrentProcess()->HasSwitch(
|
|
switches::kSchedulerBoostUrgent)) {
|
|
std::string boost_switch_str =
|
|
CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
|
|
switches::kSchedulerBoostUrgent);
|
|
|
|
int boost_switch_val;
|
|
if (!StringToInt(boost_switch_str, &boost_switch_val) ||
|
|
boost_switch_val < 0 || boost_switch_val > 100) {
|
|
DVPLOG(1) << "Invalid input for " << switches::kSchedulerBoostUrgent;
|
|
} else {
|
|
boost_def = boost_switch_val;
|
|
}
|
|
}
|
|
|
|
g_scheduler_boost_adj = GetFieldTrialParamByFeatureAsInt(
|
|
kSchedUtilHints, "BoostUrgent", boost_def);
|
|
g_scheduler_limit_adj = GetFieldTrialParamByFeatureAsInt(
|
|
kSchedUtilHints, "LimitNonUrgent", kSchedulerLimitDef);
|
|
g_scheduler_use_latency_tune_adj = GetFieldTrialParamByFeatureAsBool(
|
|
kSchedUtilHints, "LatencyTune", kSchedulerUseLatencyTuneDef);
|
|
|
|
g_scheduler_hints_adjusted.store(true);
|
|
}
|
|
#endif
|
|
|
|
void InitThreading() {}
|
|
|
|
void TerminateOnThread() {}
|
|
|
|
size_t GetDefaultThreadStackSize(const pthread_attr_t& attributes) {
|
|
#if !defined(THREAD_SANITIZER) && defined(__GLIBC__)
|
|
// Generally glibc sets ample default stack sizes, so use the default there.
|
|
return 0;
|
|
#elif !defined(THREAD_SANITIZER)
|
|
// Other libcs (uclibc, musl, etc) tend to use smaller stacks, often too small
|
|
// for chromium. Make sure we have enough space to work with here. Note that
|
|
// for comparison glibc stacks are generally around 8MB.
|
|
return 2 * (1 << 20);
|
|
#else
|
|
// ThreadSanitizer bloats the stack heavily. Evidence has been that the
|
|
// default stack size isn't enough for some browser tests.
|
|
return 2 * (1 << 23); // 2 times 8192K (the default stack size on Linux).
|
|
#endif
|
|
}
|
|
|
|
} // namespace base
|