// 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 #include #include #include #include #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 #include #include #include #include #include #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 g_use_sched_util(true); std::atomic 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(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(boost_percent) * kSchedulerUclampMax + 50) / 100; attr.sched_util_max = kSchedulerUclampMax; } else { attr.sched_util_min = kSchedulerUclampMin; attr.sched_util_max = (saturated_cast(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 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(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