1 // Copyright 2023 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 // Description: ChromeOS specific Linux code layered on top of
5 // base/threading/platform_thread_linux{,_base}.cc.
6
7 #include "base/base_switches.h"
8 #include "base/command_line.h"
9 #include "base/feature_list.h"
10 #include "base/files/file_util.h"
11 #include "base/metrics/field_trial_params.h"
12 #include "base/no_destructor.h"
13 #include "base/process/internal_linux.h"
14 #include "base/process/process.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/threading/cross_process_platform_thread_delegate.h"
17 #include "base/threading/platform_thread.h"
18 #include "base/threading/platform_thread_internal_posix.h"
19
20 #include <sys/resource.h>
21
22 namespace base {
23
24 BASE_FEATURE(kSchedUtilHints,
25 "SchedUtilHints",
26 base::FEATURE_ENABLED_BY_DEFAULT);
27
28 BASE_FEATURE(kSetThreadBgForBgProcess,
29 "SetThreadBgForBgProcess",
30 FEATURE_DISABLED_BY_DEFAULT);
31
32 BASE_FEATURE(kSetRtForDisplayThreads,
33 "SetRtForDisplayThreads",
34 FEATURE_DISABLED_BY_DEFAULT);
35 namespace {
36
37 CrossProcessPlatformThreadDelegate* g_cross_process_platform_thread_delegate =
38 nullptr;
39
40 std::atomic<bool> g_use_sched_util(true);
41 std::atomic<bool> g_scheduler_hints_adjusted(false);
42 std::atomic<bool> g_threads_bg_enabled(false);
43 std::atomic<bool> g_display_threads_rt(false);
44
45 // When a device doesn't specify uclamp values via chrome switches,
46 // default boosting for urgent tasks is hardcoded here as 20%.
47 // Higher values can lead to higher power consumption thus this value
48 // is chosen conservatively where it does not show noticeable
49 // power usage increased from several perf/power tests.
50 const int kSchedulerBoostDef = 20;
51 const int kSchedulerLimitDef = 100;
52 const bool kSchedulerUseLatencyTuneDef = true;
53
54 int g_scheduler_boost_adj;
55 int g_scheduler_limit_adj;
56 bool g_scheduler_use_latency_tune_adj;
57
58 // Defined by linux uclamp ABI of sched_setattr().
59 constexpr uint32_t kSchedulerUclampMin = 0;
60 constexpr uint32_t kSchedulerUclampMax = 1024;
61
62 // sched_attr is used to set scheduler attributes for Linux. It is not a POSIX
63 // struct and glibc does not expose it.
64 struct sched_attr {
65 uint32_t size;
66
67 uint32_t sched_policy;
68 uint64_t sched_flags;
69
70 /* SCHED_NORMAL, SCHED_BATCH */
71 int32_t sched_nice;
72
73 /* SCHED_FIFO, SCHED_RR */
74 uint32_t sched_priority;
75
76 /* SCHED_DEADLINE */
77 uint64_t sched_runtime;
78 uint64_t sched_deadline;
79 uint64_t sched_period;
80
81 /* Utilization hints */
82 uint32_t sched_util_min;
83 uint32_t sched_util_max;
84 };
85
86 #if !defined(__NR_sched_setattr)
87 #if defined(__x86_64__)
88 #define __NR_sched_setattr 314
89 #define __NR_sched_getattr 315
90 #elif defined(__i386__)
91 #define __NR_sched_setattr 351
92 #define __NR_sched_getattr 352
93 #elif defined(__arm__)
94 #define __NR_sched_setattr 380
95 #define __NR_sched_getattr 381
96 #elif defined(__aarch64__)
97 #define __NR_sched_setattr 274
98 #define __NR_sched_getattr 275
99 #else
100 #error "We don't have an __NR_sched_setattr for this architecture."
101 #endif
102 #endif
103
104 #if !defined(SCHED_FLAG_UTIL_CLAMP_MIN)
105 #define SCHED_FLAG_UTIL_CLAMP_MIN 0x20
106 #endif
107
108 #if !defined(SCHED_FLAG_UTIL_CLAMP_MAX)
109 #define SCHED_FLAG_UTIL_CLAMP_MAX 0x40
110 #endif
111
sched_getattr(pid_t pid,const struct sched_attr * attr,unsigned int size,unsigned int flags)112 long sched_getattr(pid_t pid,
113 const struct sched_attr* attr,
114 unsigned int size,
115 unsigned int flags) {
116 return syscall(__NR_sched_getattr, pid, attr, size, flags);
117 }
118
sched_setattr(pid_t pid,const struct sched_attr * attr,unsigned int flags)119 long sched_setattr(pid_t pid,
120 const struct sched_attr* attr,
121 unsigned int flags) {
122 return syscall(__NR_sched_setattr, pid, attr, flags);
123 }
124
125 // Setup whether a thread is latency sensitive. The thread_id should
126 // always be the value in the root PID namespace (see FindThreadID).
SetThreadLatencySensitivity(ProcessId process_id,PlatformThreadId thread_id,ThreadType thread_type)127 void SetThreadLatencySensitivity(ProcessId process_id,
128 PlatformThreadId thread_id,
129 ThreadType thread_type) {
130 struct sched_attr attr;
131 bool is_urgent = false;
132 int boost_percent, limit_percent;
133 int latency_sensitive_urgent;
134
135 // Scheduler boost defaults to true unless disabled.
136 if (!g_use_sched_util.load())
137 return;
138
139 // FieldTrial API can be called only once features were parsed.
140 if (g_scheduler_hints_adjusted.load()) {
141 boost_percent = g_scheduler_boost_adj;
142 limit_percent = g_scheduler_limit_adj;
143 latency_sensitive_urgent = g_scheduler_use_latency_tune_adj;
144 } else {
145 boost_percent = kSchedulerBoostDef;
146 limit_percent = kSchedulerLimitDef;
147 latency_sensitive_urgent = kSchedulerUseLatencyTuneDef;
148 }
149
150 // The thread_id passed in here is either 0 (in which case we ste for current
151 // thread), or is a tid that is not the NS tid but the global one. The
152 // conversion from NS tid to global tid is done by the callers using
153 // FindThreadID().
154 FilePath thread_dir;
155 if (thread_id && thread_id != PlatformThread::CurrentId())
156 thread_dir = FilePath(StringPrintf("/proc/%d/task/%d/", process_id, thread_id));
157 else
158 thread_dir = FilePath("/proc/thread-self/");
159
160 FilePath latency_sensitive_file = thread_dir.Append("latency_sensitive");
161
162 if (!PathExists(latency_sensitive_file))
163 return;
164
165 // Silently ignore if getattr fails due to sandboxing.
166 if (sched_getattr(thread_id, &attr, sizeof(attr), 0) == -1 ||
167 attr.size != sizeof(attr))
168 return;
169
170 switch (thread_type) {
171 case ThreadType::kBackground:
172 case ThreadType::kUtility:
173 case ThreadType::kResourceEfficient:
174 case ThreadType::kDefault:
175 break;
176 case ThreadType::kCompositing:
177 case ThreadType::kDisplayCritical:
178 // Compositing and display critical threads need a boost for consistent 60
179 // fps.
180 [[fallthrough]];
181 case ThreadType::kRealtimeAudio:
182 is_urgent = true;
183 break;
184 }
185
186 PLOG_IF(ERROR,
187 !WriteFile(latency_sensitive_file,
188 (is_urgent && latency_sensitive_urgent) ? "1" : "0", 1))
189 << "Failed to write latency file.";
190
191 attr.sched_flags |= SCHED_FLAG_UTIL_CLAMP_MIN;
192 attr.sched_flags |= SCHED_FLAG_UTIL_CLAMP_MAX;
193
194 if (is_urgent) {
195 attr.sched_util_min =
196 (saturated_cast<uint32_t>(boost_percent) * kSchedulerUclampMax + 50) /
197 100;
198 attr.sched_util_max = kSchedulerUclampMax;
199 } else {
200 attr.sched_util_min = kSchedulerUclampMin;
201 attr.sched_util_max =
202 (saturated_cast<uint32_t>(limit_percent) * kSchedulerUclampMax + 50) /
203 100;
204 }
205
206 DCHECK_GE(attr.sched_util_min, kSchedulerUclampMin);
207 DCHECK_LE(attr.sched_util_max, kSchedulerUclampMax);
208
209 attr.size = sizeof(struct sched_attr);
210 if (sched_setattr(thread_id, &attr, 0) == -1) {
211 // We log it as an error because, if the PathExists above succeeded, we
212 // expect this syscall to also work since the kernel is new'ish.
213 PLOG_IF(ERROR, errno != E2BIG)
214 << "Failed to set sched_util_min, performance may be effected.";
215 }
216 }
217
218 // Get the type by reading through kThreadTypeToNiceValueMap
GetThreadTypeForNiceValue(int nice_value)219 std::optional<ThreadType> GetThreadTypeForNiceValue(int nice_value) {
220 for (auto i : internal::kThreadTypeToNiceValueMap) {
221 if (nice_value == i.nice_value) {
222 return i.thread_type;
223 }
224 }
225 return std::nullopt;
226 }
227
GetNiceValueForThreadId(PlatformThreadId thread_id)228 std::optional<int> GetNiceValueForThreadId(PlatformThreadId thread_id) {
229 // Get the current nice value of the thread_id
230 errno = 0;
231 int nice_value = getpriority(PRIO_PROCESS, static_cast<id_t>(thread_id));
232 if (nice_value == -1 && errno != 0) {
233 // The thread may disappear for any reason so ignore ESRCH.
234 DVPLOG_IF(1, errno != ESRCH)
235 << "Failed to call getpriority for thread id " << thread_id
236 << ", performance may be effected.";
237 return std::nullopt;
238 }
239 return nice_value;
240 }
241
242 } // namespace
243
SetThreadTypeOtherAttrs(ProcessId process_id,PlatformThreadId thread_id,ThreadType thread_type)244 void SetThreadTypeOtherAttrs(ProcessId process_id,
245 PlatformThreadId thread_id,
246 ThreadType thread_type) {
247 // For cpuset and legacy schedtune interface
248 PlatformThreadLinux::SetThreadCgroupsForThreadType(thread_id, thread_type);
249
250 // For upstream uclamp interface. We try both legacy (schedtune, as done
251 // earlier) and upstream (uclamp) interfaces, and whichever succeeds wins.
252 SetThreadLatencySensitivity(process_id, thread_id, thread_type);
253 }
254
255 // Set or reset the RT priority of a thread based on its type
256 // and whether the process it is in is backgrounded.
257 // Setting an RT task to CFS retains the task's nice value.
SetThreadRTPrioFromType(ProcessId process_id,PlatformThreadId thread_id,ThreadType thread_type,bool proc_bg)258 void SetThreadRTPrioFromType(ProcessId process_id,
259 PlatformThreadId thread_id,
260 ThreadType thread_type,
261 bool proc_bg) {
262 struct sched_param prio;
263 int policy;
264
265 switch (thread_type) {
266 case ThreadType::kRealtimeAudio:
267 prio = PlatformThreadChromeOS::kRealTimeAudioPrio;
268 policy = SCHED_RR;
269 break;
270 case ThreadType::kCompositing:
271 [[fallthrough]];
272 case ThreadType::kDisplayCritical:
273 if (!PlatformThreadChromeOS::IsDisplayThreadsRtFeatureEnabled()) {
274 return;
275 }
276 if (proc_bg) {
277 // Per manpage, must be 0. Otherwise could have passed nice value here.
278 // Note that even though the prio.sched_priority passed to the
279 // sched_setscheduler() syscall is 0, the old nice value (which holds the
280 // ThreadType of the thread) is retained.
281 prio.sched_priority = 0;
282 policy = SCHED_OTHER;
283 } else {
284 prio = PlatformThreadChromeOS::kRealTimeDisplayPrio;
285 policy = SCHED_RR;
286 }
287 break;
288 default:
289 return;
290 }
291
292 PlatformThreadId syscall_tid = thread_id == PlatformThread::CurrentId() ? 0 : thread_id;
293 if (sched_setscheduler(syscall_tid, policy, &prio) != 0) {
294 DVPLOG(1) << "Failed to set policy/priority for thread " << thread_id;
295 }
296 }
297
SetThreadNiceFromType(ProcessId process_id,PlatformThreadId thread_id,ThreadType thread_type)298 void SetThreadNiceFromType(ProcessId process_id,
299 PlatformThreadId thread_id,
300 ThreadType thread_type) {
301 PlatformThreadId syscall_tid = thread_id == PlatformThread::CurrentId() ? 0 : thread_id;
302 const int nice_setting = internal::ThreadTypeToNiceValue(thread_type);
303 if (setpriority(PRIO_PROCESS, static_cast<id_t>(syscall_tid), nice_setting)) {
304 DVPLOG(1) << "Failed to set nice value of thread " << thread_id << " to "
305 << nice_setting;
306 }
307 }
308
InitializeFeatures()309 void PlatformThreadChromeOS::InitializeFeatures() {
310 DCHECK(FeatureList::GetInstance());
311 g_threads_bg_enabled.store(FeatureList::IsEnabled(kSetThreadBgForBgProcess));
312 g_display_threads_rt.store(FeatureList::IsEnabled(kSetRtForDisplayThreads));
313 if (!FeatureList::IsEnabled(kSchedUtilHints)) {
314 g_use_sched_util.store(false);
315 return;
316 }
317
318 int boost_def = kSchedulerBoostDef;
319
320 if (CommandLine::ForCurrentProcess()->HasSwitch(
321 switches::kSchedulerBoostUrgent)) {
322 std::string boost_switch_str =
323 CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
324 switches::kSchedulerBoostUrgent);
325
326 int boost_switch_val;
327 if (!StringToInt(boost_switch_str, &boost_switch_val) ||
328 boost_switch_val < 0 || boost_switch_val > 100) {
329 DVLOG(1) << "Invalid input for " << switches::kSchedulerBoostUrgent;
330 } else {
331 boost_def = boost_switch_val;
332 }
333 }
334
335 g_scheduler_boost_adj = GetFieldTrialParamByFeatureAsInt(
336 kSchedUtilHints, "BoostUrgent", boost_def);
337 g_scheduler_limit_adj = GetFieldTrialParamByFeatureAsInt(
338 kSchedUtilHints, "LimitNonUrgent", kSchedulerLimitDef);
339 g_scheduler_use_latency_tune_adj = GetFieldTrialParamByFeatureAsBool(
340 kSchedUtilHints, "LatencyTune", kSchedulerUseLatencyTuneDef);
341
342 g_scheduler_hints_adjusted.store(true);
343 }
344
345 // static
SetCrossProcessPlatformThreadDelegate(CrossProcessPlatformThreadDelegate * delegate)346 void PlatformThreadChromeOS::SetCrossProcessPlatformThreadDelegate(
347 CrossProcessPlatformThreadDelegate* delegate) {
348 // A component cannot override a delegate set by another component, thus
349 // disallow setting a delegate when one already exists.
350 DCHECK_NE(!!g_cross_process_platform_thread_delegate, !!delegate);
351
352 g_cross_process_platform_thread_delegate = delegate;
353 }
354
355 // static
IsThreadsBgFeatureEnabled()356 bool PlatformThreadChromeOS::IsThreadsBgFeatureEnabled() {
357 return g_threads_bg_enabled.load();
358 }
359
360 // static
IsDisplayThreadsRtFeatureEnabled()361 bool PlatformThreadChromeOS::IsDisplayThreadsRtFeatureEnabled() {
362 return g_display_threads_rt.load();
363 }
364
365 // static
GetThreadTypeFromThreadId(ProcessId process_id,PlatformThreadId thread_id)366 std::optional<ThreadType> PlatformThreadChromeOS::GetThreadTypeFromThreadId(
367 ProcessId process_id,
368 PlatformThreadId thread_id) {
369 // Get the current nice_value of the thread_id
370 std::optional<int> nice_value = GetNiceValueForThreadId(thread_id);
371 if (!nice_value.has_value()) {
372 return std::nullopt;
373 }
374 return GetThreadTypeForNiceValue(nice_value.value());
375 }
376
377 // static
SetThreadType(ProcessId process_id,PlatformThreadId thread_id,ThreadType thread_type,IsViaIPC via_ipc)378 void PlatformThreadChromeOS::SetThreadType(ProcessId process_id,
379 PlatformThreadId thread_id,
380 ThreadType thread_type,
381 IsViaIPC via_ipc) {
382 if (g_cross_process_platform_thread_delegate &&
383 g_cross_process_platform_thread_delegate->HandleThreadTypeChange(
384 process_id, thread_id, thread_type)) {
385 return;
386 }
387 SetThreadTypeInternal(process_id, thread_id, thread_type, via_ipc);
388 }
389
390 // static
SetThreadTypeInternal(ProcessId process_id,PlatformThreadId thread_id,ThreadType thread_type,IsViaIPC via_ipc)391 void PlatformThreadChromeOS::SetThreadTypeInternal(ProcessId process_id,
392 PlatformThreadId thread_id,
393 ThreadType thread_type,
394 IsViaIPC via_ipc) {
395 // TODO(b/262267726): Re-use common code with PlatformThreadLinux::SetThreadType
396 // Should not be called concurrently with other functions
397 // like SetThreadBackgrounded.
398 if (via_ipc) {
399 DCHECK_CALLED_ON_VALID_SEQUENCE(
400 PlatformThread::GetCrossProcessThreadPrioritySequenceChecker());
401 }
402
403 auto proc = Process::Open(process_id);
404 bool backgrounded = false;
405 if (IsThreadsBgFeatureEnabled() &&
406 thread_type != ThreadType::kRealtimeAudio && proc.IsValid() &&
407 proc.GetPriority() == base::Process::Priority::kBestEffort) {
408 backgrounded = true;
409 }
410
411 SetThreadTypeOtherAttrs(process_id, thread_id,
412 backgrounded ? ThreadType::kBackground : thread_type);
413
414 SetThreadRTPrioFromType(process_id, thread_id, thread_type, backgrounded);
415 SetThreadNiceFromType(process_id, thread_id, thread_type);
416 }
417
SetThreadBackgrounded(ProcessId process_id,PlatformThreadId thread_id,bool backgrounded)418 void PlatformThreadChromeOS::SetThreadBackgrounded(ProcessId process_id,
419 PlatformThreadId thread_id,
420 bool backgrounded) {
421 // Get the current nice value of the thread_id
422 std::optional<int> nice_value = GetNiceValueForThreadId(thread_id);
423 if (!nice_value.has_value()) {
424 return;
425 }
426
427 std::optional<ThreadType> type =
428 GetThreadTypeForNiceValue(nice_value.value());
429 if (!type.has_value()) {
430 return;
431 }
432
433 // kRealtimeAudio threads are not backgrounded or foregrounded.
434 if (type == ThreadType::kRealtimeAudio) {
435 return;
436 }
437
438 SetThreadTypeOtherAttrs(
439 process_id, thread_id,
440 backgrounded ? ThreadType::kBackground : type.value());
441 SetThreadRTPrioFromType(process_id, thread_id, type.value(), backgrounded);
442 }
443
444 SequenceCheckerImpl&
GetCrossProcessThreadPrioritySequenceChecker()445 PlatformThreadChromeOS::GetCrossProcessThreadPrioritySequenceChecker() {
446 // In order to use a NoDestructor instance, use SequenceCheckerImpl instead of
447 // SequenceCheckerDoNothing because SequenceCheckerImpl is trivially
448 // destructible but SequenceCheckerDoNothing isn't.
449 static NoDestructor<SequenceCheckerImpl> instance;
450 return *instance;
451 }
452
453 } // namespace base
454