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