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